I have the following CoreData stack:
@MainActor
final class CoreDataStack {
static let shared = CoreDataStack()
let privateContext: NSManagedObjectContext //used for database read/writes, background queue
let mainContext: NSManagedObjectContext //parent is set to privateContext, operates on main queue, used for UI
.....
}
I am omitting initialisation as it is not relevant here.
Now I want to define a CloudActor whose job would be to sync data with the cloud service. The most obvious choice would be to restrict this actor to the privateContext, but I struggle to figure out how to achieve this:
final class NSModelObjectContextExecutor: @unchecked Sendable, SerialExecutor {
final let context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func enqueue(_ job: UnownedJob) {
let unownedExecutor = asUnownedSerialExecutor()
context.perform {
job.runSynchronously(on: unownedExecutor)
}
}
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
}
final actor CloudDelegate {
static let shared = CloudDelegate()
private let context:NSManagedObjectContext
init() {
// this doesn't work because CoreDataStack is isolated to MainActor
context == CoreDataStack.shared.privateContext
}
}
I am using strict concurrency, so I struggle to understand how I can achieve this. I thought about moving initialisation of privateContext to the CloudDelegate, but it just pushes the problem to CoreDataStack where I would lose the ability to call mainContext.parent = privateContext.
At the same time I realise that what I am trying to achieve seems completely reasonable, so there must be a way to do so. I am probably just missing something.
At the moment the workaround I have is to initialise a fresh background context in CloudDelegate which connects directly to the persistent coordinator, but this is obviously less efficient and introduces weird complications with merging (see this)
initbeasync. Now it canawaitthe fetching of theprivateContext. You cannot put a Task into aninitbut you can makeinititself beasync.static let shared = CloudDelegate(). However, it look like async init is EXACTLY what is needed. “Wait till you get access to a context property defined on MainActor, then you are ready to go”. Will test tomorrow and let you know :)