Right now I'm in the process of fixing my project up for Swift 6 and around 90% of my concurrency and race condition warnings come from usage of Core Data, mostly context.perform calls to be precise.
To easily update certain aspects of a managed object I used to have extensions that would easily make this possible, such as Item.updateTimestamp(). This was likely always bad, but now Swift 6 complains.
Here's a sample of what I'm talking about.
Core Data's NSManagedObject is a difficult beast for me anyway because of all the things you need to consider when multithreaded. I'm getting to grips with all the Swift 6 changes slowly but here I'm stomped on what to do really if I want to keep my extensions approach. Is there any good solution to keep it working like this or is my proposed solution with the DataManager class the best way?
I guess the worst hack would be this, which would make it work again, but would still be unsafe then.
extension Item: @unchecked Sendable { }
Any help would be appreciated. Thanks!
class TestViewController: UIViewController {
let item: Item = Item() // NSManagedObject
override func viewDidLoad() {
super.viewDidLoad()
Task {
// My current way to do general operations on a managed object.
// Error: Sending 'self.item' risks causing data races
await item.updateTimestamp()
// Using a seperate class function by passing the object id works, but is 'ugly'.
await DataManager().updateTimestamp(objectID: item.objectID)
}
}
}
extension Item {
func updateTimestamp() async {
let context = PersistenceController.shared.container.newBackgroundContext()
await context.perform {
self.timestamp = .now
try! context.save()
}
}
}
class DataManager {
func updateTimestamp(objectID: NSManagedObjectID) async {
let context = PersistenceController.shared.container.newBackgroundContext()
await context.perform {
let item = context.object(with: objectID) as! Item
item.timestamp = .now
try! context.save()
}
}
}