-1

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)

2
  • 1
    "this doesn't work because CoreDataStack is isolated to MainActor" If that's the problem, there is a solution so simple and obvious there must be something wrong with it: make the actor init be async. Now it can await the fetching of the privateContext. You cannot put a Task into an init but you can make init itself be async. Commented Nov 9 at 15:08
  • @matt thank you so much! As I said I was sure that I missed something, and async init for actors seems like the thing :) I wonder if I’ll be able to retain a singleton pattern with 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 :) Commented Nov 9 at 22:08

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.