swiftcore-datacloudkit

Use CloudKit with CoreData store in shared AppGroup container


Because I added widgets that need access to the CoreData store, I have moved the store into a shared AppGroup container. That has worked well, with no issues. However, I have been unable to get it to sync with iCloud. I can sync a store in the app's storage just fine.

My PersistenceController is as follows:

struct PersistenceController {

    static let shared = PersistenceController()

    let context: NSManagedObjectContext
    let container: NSPersistentCloudKitContainer
    let coordinator: NSPersistentStoreCoordinator

    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: CDConstants.containerName)

        let url = AppGroup.group.containerURL.appendingPathComponent(CDConstants.containerNameSQLite)
        let description = NSPersistentStoreDescription(url: url)
        description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
            containerIdentifier: CDConstants.containerName
        )

        container.persistentStoreDescriptions = [description]
        context = container.viewContext
        context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
        coordinator = container.persistentStoreCoordinator

        // MARK: UndoManager Support
        container.viewContext.undoManager = UndoManager()
    
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as? NSError {
                print("loadPersistentStores: error \(error), \(error.userInfo) for store \(storeDescription.description)")
            }
        })
    
        // MARK: Cloudkit Merge
        context.automaticallyMergesChangesFromParent = true
        
        // MARK: Cloudkit Schema Initialization
#if DEBUG
//        do {
//            try container.initializeCloudKitSchema(options: [.printSchema])
//        } catch {
//            PersistenceController.logger.error("Schema initialization error: \(error)")
//        }
#endif
    }
}

I have debugging turned on for CoreData and I get this in the console:

CoreData: debug: CoreData+CloudKit: -[PFCloudKitOptionsValidator validateOptions:andStoreOptions:error:](36): Validating options: <NSCloudKitMirroringDelegateOptions: 0x600003000ab0> containerIdentifier:containerName databaseScope:Private ckAssetThresholdBytes:<null> operationMemoryThresholdBytes:<null> useEncryptedStorage:NO useDeviceToDeviceEncryption:NO automaticallyDownloadFileBackedFutures:NO automaticallyScheduleImportAndExportOperations:YES skipCloudKitSetup:NO preserveLegacyRecordMetadataBehavior:NO useDaemon:YES apsConnectionMachServiceName:<null> containerProvider:<PFCloudKitContainerProvider: 0x6000000083b0> storeMonitorProvider:<PFCloudKitStoreMonitorProvider: 0x6000000083c0> metricsClient:<PFCloudKitMetricsClient: 0x6000000083d0> metadataPurger:<PFCloudKitMetadataPurger: 0x6000000083e0> scheduler:<null> notificationListener:<null> containerOptions:<null> defaultOperationConfiguration:<null> progressProvider:<NSPersistentCloudKitContainer: 0x600001744540> test_useLegacySavePolicy:YES archivingUtilities:<PFCloudKitArchivingUtilities: 0x6000000083f0> bypassSchedulerActivityForInitialImport:NO bypassDasdRateLimiting:NO
storeOptions: {
    NSInferMappingModelAutomaticallyOption = 1;
    NSMigratePersistentStoresAutomaticallyOption = 1;
    NSPersistentCloudKitContainerOptionsKey = "<NSPersistentCloudKitContainerOptions: 0x6000021035c0>";
    NSPersistentHistoryTrackingKey = 1;
    NSPersistentStoreMirroringOptionsKey =     {
        NSPersistentStoreMirroringDelegateOptionKey = "<NSCloudKitMirroringDelegate: 0x600003d00000>";
    };
    NSPersistentStoreRemoteChangeNotificationOptionKey = 1;
}
CoreData: debug: CoreData+CloudKit: -[NSCloudKitMirroringDelegate observeChangesForStore:inPersistentStoreCoordinator:](423): <NSCloudKitMirroringDelegate: 0x600003d00000>: Observing store: <NSSQLCore: 0x107a0be80> (URL: file:///~/Library/Developer/CoreSimulator/Devices/F475D0D5-EA93-498B-B7C5-007160ACDA56/data/Containers/Shared/AppGroup/C5337C45-86EF-4D32-80B8-E89D28A2336E/containerName.sqlite)
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _setUpCloudKitIntegration:](584): <NSCloudKitMirroringDelegate: 0x600003d00000>: Successfully enqueued setup request: <NSCloudKitMirroringDelegateSetupRequest: 0x600002129ae0> CFEDB8FA-CFBB-46CD-A2F7-5E1C64EFDEA6
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate checkAndExecuteNextRequest](3535): <NSCloudKitMirroringDelegate: 0x600003d00000>: Checking for pending requests.
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate checkAndExecuteNextRequest]_block_invoke(3548): <NSCloudKitMirroringDelegate: 0x600003d00000>: Executing: <NSCloudKitMirroringDelegateSetupRequest: 0x600002129ae0> CFEDB8FA-CFBB-46CD-A2F7-5E1C64EFDEA6

CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](452): Skipping migration for 'ANSCKDATABASEMETADATA' because it already has a column named 'ZLASTFETCHDATE'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](510): Skipping migration for 'ANSCKMETADATAENTRY' because it already has a column named 'ZDATEVALUE'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](452): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZLASTFETCHDATE'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](468): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZSUPPORTSFETCHCHANGES'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](468): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZSUPPORTSATOMICCHANGES'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](468): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZSUPPORTSRECORDSHARING'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](468): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZSUPPORTSZONESHARING'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZNEEDSIMPORT'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZNEEDSRECOVERYFROMZONEDELETE'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZNEEDSRECOVERYFROMUSERPURGE'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZENCODEDSHAREDATA'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZNEEDSSHAREUPDATE'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZNEEDSSHAREDELETE'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZNEEDSRECOVERYFROMIDENTITYLOSS'
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](486): Skipping migration for 'ANSCKRECORDZONEMETADATA' because it already has a column named 'ZNEEDSNEWSHAREINVITATION'

The errors I am getting back from trying to initialize the store are these:

Error fetching user record ID: <CKError 0x600000c8cf60: "Bad Container" (1014); "Couldn't get container configuration from the server for container "containerName"">

error: CoreData+CloudKit: -[PFCloudKitSetupAssistant _checkUserIdentity:](1571): <PFCloudKitSetupAssistant: 0x600002620120>: Identity fetch failed with unknown error: <CKError 0x600000ca0000: "Bad Container" (5/1014); "Couldn't get container configuration from the server for container "containerName"">

error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _performSetupRequest:]_block_invoke(1232): <NSCloudKitMirroringDelegate: 0x600003d00000>: Failed to set up CloudKit integration for store: <NSSQLCore: 0x107a0be80> (URL: file:///~/Library/Developer/CoreSimulator/Devices/F475D0D5-EA93-498B-B7C5-007160ACDA56/data/Containers/Shared/AppGroup/C5337C45-86EF-4D32-80B8-E89D28A2336E/containerName.sqlite)

Obviously, I am missing something, but I have no idea what.


Solution

  • Okay, it turns out this is the line that was incorrect:

    description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
        containerIdentifier: CDConstants.containerName
    )
    

    The issue is I was putting the local container name in, instead of the iCloud container name. So, instead of localContainerName, for example, it should have been iCloud.com.app. localContainerName.

    Thanks to this answer: https://stackoverflow.com/a/64359268/7129318 for guiding me in the correct direction.