NSPersistentCloudKitContainer: Share a single database with App Extension

1.4k Views Asked by At

I know this is a common question, maybe it is so many time asked before. Also I know how to share a single database with App Extensions using Core Data by enabling App Groups. I am now using the NSPersistentCloudKitContainer to sync the data (across iCloud devices), and works very well.

But I want to share my database with my App Extensions (Today Extension), at this time App Groups won't help anymore, because they are different technologies as Apple described. So I am simply enabled iCloud for both the container app and the app extension from Signing and Capabilities pane. Then I can access my single database from both app container/extension as shown below:

// MARK: - Core Data stack, Shared database
lazy var persistentContainer: NSPersistentCloudKitContainer = {
    let container = NSPersistentCloudKitContainer(name: "MyApp")

    // Spotlight search support and history tracking
    container.persistentStoreDescriptions.forEach {
        $0.setOption(CoreDataSpotlightSearchDelegate.init(forStoreWith: $0, model: container.managedObjectModel), forKey: NSCoreDataCoreSpotlightExporter)
        $0.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
    }
    
    // Load persistent store
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })

    container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    container.viewContext.automaticallyMergesChangesFromParent = true
    
    return container
}()

However it is only works when the device is online (which is expected behavior), so my question is:

Is it possible to share my database between app and extension while the device is offline using NSPersistentCloudKitContainer? Some workarounds?

EDIT

This is a non-working version, I can access the same database but without cloud-syncing (Not appears on the CloudKit Dashboard):

 // MARK: - Core Data stack, Shared database
lazy var persistentContainer: NSPersistentCloudKitContainer = {
    let container = NSPersistentCloudKitContainer(name: "MyApp")
    
    // Shared container support
    if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.company.MyApp")?.appendingPathComponent("MyApp.sqlite") {
        let persistentStoreDescription = NSPersistentStoreDescription.init(url: url)
        container.persistentStoreDescriptions = [persistentStoreDescription]
    }

    // Spotlight search support and history tracking
    container.persistentStoreDescriptions.forEach {
        $0.setOption(CoreDataSpotlightSearchDelegate.init(forStoreWith: $0, model: container.managedObjectModel), forKey: NSCoreDataCoreSpotlightExporter)
    }
    
    // Load persistent store
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })

    container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    container.viewContext.automaticallyMergesChangesFromParent = true
    
    return container
}()
1

There are 1 best solutions below

10
malhal On

To share a database between an app and extension you need to fully implement Persistent History Tracking; it is not as simple as enabling the store option. For an introduction to the feature see WWDC 2017 What's New in Core Data at 20:49 and also see the documentation Consuming Relevant Store Changes in particular "Merge Relevant Transactions into a Context".

My theory on why you weren't noticing a problem when using the NSPersistentCloudKitContainer is when your store was online and syncing with the cloud is you were benefiting of a side effect of the cloud sync running in your app process and editing the store upon resuming which in turn caused your context to get updated.

And by the way, the NSPersistentHistoryTrackingKey option is set by default for NSPersistentCloudKitContainer's store descriptions that have cloudKitContainerOptions like the default first store description has.