合并:发布者有时会失去价值并完成 [英] Combine: Publisher sometimes loses value and completes
问题描述
我有一个简单的 Deferred Publisher
,它可以从磁盘读取数据,并在 SwiftUI列表
中显示数据, Publisher
效果最佳有时,但是 有时 表现不佳,只是失去了它的值(这是 Model
对象的数组)并完成了完成
消息.我已经尝试过提及
如果我在使用者函数 readData()
中的 sink
之前添加任何下游发布者,例如 combineLatest
,将异步发布者(readFromBundle)与同步发布者( combineLatest
)链接在一起,将导致该值根本无法在 iOS 13.3 +
设备上交付,有时会在解决方案
看起来像是赛车类问题,请尝试以下操作(仅通过阅读代码即可)
1)明确使用后台队列
private let readQueue = DispatchQueue(label:"ReadQueue",qos:.background,属性:.concurrent)
2)将发布程序安排在此队列中,而不是在其上接收
.subscribe(on:self.readQueue)
I have a simple Deferred Publisher
that reads data from disk and I display the data in a SwiftUI List
, the Publisher
works well most of the time, but sometimes it doesn't behave well, it just loses its value (which's an array of Model
objects) and completes with finished
message. I've tried a workaround mentioned here to use the buffer
operator to keep the value in buffer because I believe the Combine's Publisher
by design won't pass the data downstream if there is no demand requested by subscribers and hence dropping this data and completes, however using buffer
didn't solve the issue.
The code I have:
enum FileError: Error {
case someError
}
class ViewModel: ObservableObject {
@Published var modelArray = [Model]()
private var subscriptions = Set<AnyCancellable>()
func readData() {
DataSource()
.readFromBundle(resource: "Sample", type: "json")
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
print("Completion: \(completion)")
}) { array in
self.modelArray = array
}.store(in: &subscriptions)
}
}
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
List(self.viewModel.modelArray) { model in
Text("\(model.name)")
}
}
.onAppear {
self.viewModel.readData()
}
}
}
struct Model: Codable, Identifiable {
var id: Int
var name: String
}
class DataSource {
private let readQueue = DispatchQueue(label: "ReadQueue", qos: .default, attributes: .concurrent)
func readFromBundle (resource: String, type:String) -> AnyPublisher<[Model], FileError> {
Deferred {
Future { promise in
guard let url = Bundle.main.url(forResource: "Sample", withExtension: "json"),
let data = try? Data(contentsOf: url),
let modelArray = try? JSONDecoder().decode([Model].self, from: data)
else {
promise(.failure(.someError))
return
}
promise(.success(modelArray))
}
}
.receive(on: self.readQueue)
.eraseToAnyPublisher()
}
}
This is a link to download a working sample project.
EDIT:
Environment: Xcode 11.3.1, iOS 13.3 iPhone 11 Pro Max simulator and device.
gif screenshot (notice the console output)
EDIT2:
if I add any downstream publishers, like combineLatest
for example just before sink
in the consumer function readData()
then a new behavior introduced, which's chaining an async publisher (readFromBundle) with a sync publisher (combineLatest
) will result in the value will not deliver at all on iOS 13.3+
devices and will sometimes deliver on devices below iOS 13.3
, as stated on this link.
It looks like racing-kind issue, please try the following (just by code-reading)
1) use background queue explicitly
private let readQueue = DispatchQueue(label: "ReadQueue", qos: .background,
attributes: .concurrent)
2) schedule Publisher on this queue instead of receiving on it
.subscribe(on: self.readQueue)
这篇关于合并:发布者有时会失去价值并完成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!