Search code examples
iosswiftcore-datansmanagedobjectcontext

Access CoreData objects created by a private managedObjectContext (created in a background thread)


I have this dummy setup (for testing out feasibility). Idea is: When the game first starts I need to copy a whole bunch of data (stored in plists) to the database using CoreData. I want to do this inside a global background queue (so that the UI is still responsive).

That's what I try to do:

appDelegate.persistentContainer.performBackgroundTask { (managedObjectContext) in
    GameSetupController.setup(withManagedObjectContext: managedObjectContext)
}

I start a backgroundTask and call a method on my GameSetupController. It looks like this:

static func setup(withManagedObjectContext managedObjectContext: NSManagedObjectContext) {
    let startCoords = Coordinates(atQuadrant: 153, galaxy: 42, system: 3, using: managedObjectContext)
    let startPlanet = Planet(atCoordinates: startCoords, withName: "Planet 1", using: managedObjectContext)
    let startBase = Site(atPlanet: startPlanet, withName: "Name of base", belongingToPlayer: true, using: managedObjectContext)

    // Game is a singleton
    Game.shared.currentSite = startBase

    do {
        try managedObjectContext.save()
    } catch let error as NSError {
        fatalError("Couldn't save to database. Error: \(error.debugDescription)")
    }
}

The Game singleton is used to store current game properties, such as the Site the user is currently located.

After the background task has finished, I want to print Game.shared.currentSite (from the main queue). That's what I get:

Optional(<Site: 0x17008c080> (entity: Site; id: 0xd000000000100004 <x-coredata://5D8B1335-5F8F-4E66-BC3F-DA7C7AA2B54B/Site/p4> ; data: < fault >))

Normally (that's my understanding of CoreData), if you access a faulted NSManagedObject it fires and loads the data. But here, this doesn't happen.

However, if I run GameSetupController.setup(withManagedObjectContext: managedObjectContext) in the main queue (using the managedObjectContext that's declared there) I get this:

Optional(<Site: 0x170092610> (entity: Site; id: 0xd000000000140004 <x-coredata://5D8B1335-5F8F-4E66-BC3F-DA7C7AA2B54B/Site/p5> ; data: {
    belongsToPlayer = 1;
    buildings =     (
    );
    coordinates = "0xd000000000140000 <x-coredata://5D8B1335-5F8F-4E66-BC3F-DA7C7AA2B54B/Coordinates/p5>";
    events =     (
    );
    name = "Main Base I";
    planet = "0xd000000000140002 <x-coredata://5D8B1335-5F8F-4E66-BC3F-DA7C7AA2B54B/Planet/p5>";
    resources = nil;
    spaceships =     (
    );
}))

That's what I want. How can I get this result when using the private managedObjectContext in a background queue? Is it even possible?

Let me know if you need any more code or information. Thanks!


Solution

  • The method you are using, performBackgroundTask actually

    creates a new NSManagedObjectContext with the concurrencyType set to NSPrivateQueueConcurrencyType.

    Source

    It means that this context will likely get deallocated by the time you print the Game.shared.currentSite object. And since it was created inside this no longer existing context, there's no way to un-fault it.

    Basically your idea to populate the database in the background thread is good and correct, you just simply have to fetch the Game.shared.currentSite object from the main context so that it'll live on while the app is running.

    UPDATE:

    Also, if you're using print method to debug CoreData objects, remember that it won't cause them to un-fault. You can try printing some of their properties instead - that should show you if un-faulting went fine.