Post

Replies

Boosts

Views

Activity

Reply to MainActor.run failing to run closure when called from within a detached task
Is this the same problem that's impacting the following code snippet: let urls: [URL] = [url1, url2, url3, ... ] // Image we have 1,000s of URLs here await withTaskGroup(of: CGImageSource.self) { taskGroup in for url in urls { taskGroup.addTask { return CGImageSourceCreateWithURL(url as CFURL, nil) } } var results = [CGImageSource]() for await result in taskGroup { results.append(result) } return results } I don't have a way to await the call to CGImageSourceCreateWithURL - this code blocks up.
Topic: Programming Languages SubTopic: Swift Tags:
Sep ’21
Reply to MainActor.run failing to run closure when called from within a detached task
Having looked and thought carefully about this, I have found that adding Task.yield() solves the issue, as I proactively await: final class NewFileCounter: ObservableObject { @Published var fileCount = 0 func findImagesInFolder(_ folderURL: URL) { let fileManager = FileManager.default Task.detached { var foundFileCount = 0 let options = FileManager.DirectoryEnumerationOptions(arrayLiteral: [.skipsHiddenFiles, .skipsPackageDescendants]) if let enumerator = fileManager.enumerator(at: folderURL, includingPropertiesForKeys: [], options: options) { while let _ = enumerator.nextObject() as? URL { foundFileCount += 1 await Task.yield() if foundFileCount % 10_000 == 0 { let fileCount = foundFileCount await MainActor.run { self.fileCount = fileCount } } } let fileCount = foundFileCount await MainActor.run { self.fileCount = fileCount } } } } }
Topic: Programming Languages SubTopic: Swift Tags:
Aug ’21
Reply to Accessing an actor's isolated state from within a SwiftUI view
One thing to note here - tying your whole mode to the main actor has consequences: for example not being able to conform it to Encodable for one. A more nuanced approach is to create a global actor, and annotate only the properties that need to be isolated in the view model. I discuss this more here: SwiftUI macOS document app architecture in a concurrent world
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’21
Reply to SwiftUI macOS document app architecture in a concurrent world
I've worked out a solution - it's taken my way too long: I feel daft looking back on my first clumsy attempt. Two of my key mistakes were to put the my RecordViewModel object (which needed to conform to ObservableObject) on the MainActor, and to create an actor type, RecordsModel, to hold the property that the needed to be isolated. Here's the original attempt: actor RecordsModel: Decodable { var records: [Record] = [] enum CodingKeys: String, CodingKey { case records } init() {} init(from decoder: Decoder) async throws { ... } // Unable to conform to Encodable at present with this implementation func encode(to encoder: Encoder) throws { ... } func addRecord() -> [Record] { self.records.append(Record(value: Int.random(in: 0...10))) // Assume it takes a long time to compute `value` return self.records } } @MainActor class RecordsViewModel: ObservableObject { @Published var records: [Record] private let recordsModel: RecordsModel init() { self.records = [] self.recordsModel = RecordsModel() } init(fromRecordsModel recordsModel: RecordsModel) async { self.records = await recordsModel.records self.recordsModel = recordsModel } func addRecord() { // Given addRecord takes time to complete, we run it in the background Task { self.records = await recordsModel.addRecord() } } } My new approach doesn't create an actor, but puts a property isolatedRecords in the view model, isolated with a global actor. This is complimented by a non-isolated published version, which is updated on the MainActor after any updates to its isolated twin. Here's the new view model class: final class Records: ObservableObject, Codable { @Published var records: [Record] @MyActor private var isolatedRecords: [Record] init() { self.records = [] self.isolatedRecords = [] } enum CodingKeys: String, CodingKey { case records } init(from decoder: Decoder) throws { ... } func encode(to encoder: Encoder) throws { ... } @MyActor func append(_ value: Int) -> [Record] { self.isolatedRecords.append(Record(value)) return isolatedRecords } func addRecord() { Task() { let newNumber = Int.random(in: 0...10) // Assume lots of processing here, hence we run it as a Task let newRecords = await self.append(newNumber) await MainActor.run { self.records = newRecords } } } } This has succeeded in ensuring the code is free of race-conditions, whilst keeping processing and update syncronisation of the record's array off the main thread, and removing all the other issues I encountered such as trying to conform the actor to Encodable, putting the Document on the MainActor and more. The code is more elegant also. I've left the full updated project at GitHub
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’21
Reply to Conforming @MainActor class to Codable
I've worked out that I need to change init and encode to async functions: @MainActor final class MyClass: Codable { var value: Int enum CodingKeys: String, CodingKey { case value } init(from decoder: Decoder) async throws { let data = try decoder.container(keyedBy: CodingKeys.self) self.value = try data.decode(Int.self, forKey: .value) } func encode(to encoder: Encoder) async throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(value, forKey: .value) } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’21