My app contains a resource heavy operation that populates an Array based on data pulled from an XML feed. I do not want this operation to lock up the main thread (and the UI when the array is given new data), so it's done in the background.
let dispatchQueue = DispatchQueue(label: "concurrent.queue", qos: .utility, attributes: .concurrent)
class XMLHandler: ObservableObject {
let context: NSManagedObjectContext
@Published var myArray: [CustomObject] = []
init(context: NSManagedObjectContext) {
self.context = context
}
...some code...
func populateArray {
dispatchQueue.async {
...xml parsing happens...
(xmlOutputObject) in
for x in xmlOutputObject {
self.myArray.append(x) }
}
Elsewhere, my SwiftUI View uses myArray to populate it's List:
struct MyView: View {
@EnvironmentObject var handler: XMLHandler
var body: some View {
List{
ForEach(handler.myArray) { CustomObject in
... generate rows ...
}
}
}
My error on runtime occurs when my app tries to update @Published var myArray: [CustomObject] = [].
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
I know this is something to do with adopting Combine, but I honestly have no idea where to start. Any help would be appreciated.
I simply want the following to happen:
Just put @MainActor
before defining your class that is supposed to act in the main thread. And it's so simple in terms of using new Swift concurrency.
@MainActor class DocumentsViewModel: ObservableObject { ... }
There's a lot of information about that in new WWDC 2021 videos or articles like that: Using the MainActor attribute to automatically dispatch UI updates on the main queue
Since the appending happens in a loop, you need to decide if you want to emit a new value once per item, or once for the whole update. If you're not certain, update it once for the whole update.
Then perform that action on the main queue:
...xml parsing happens...
(xmlOutputObject) in
DispatchQueue.main.async { // <====
self.append(contentsOf: xmlOutputObject)
}
The key point is that you cannot read or write properties on multiple queues. And in this case the queue you want to use is the main one (because it's driving the UI). So you must make sure that all property access happen on that queue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With