Search code examples
iosswiftcore-datacloudkit

Swift CloudKit CoreData Remote Change Notification also firing for local changes


I have a CoreData setup with a Local store and a Cloud store, only the latter of which syncs with CloudKit. The sync itself works well.

I'm trying to listen for remote change notifications so that I can trigger some processes on device when a remote change from CloudKit comes in. I've set up the NSPersistentStoreRemoteChangeNotificationOptionKey key in my persistence controller, and a corresponding observer that listens for this.

The observer correctly triggers for remote change notifications, however my problem is that is also triggers for any CoreData changes that originate on-device once the view context is saved.

If this is expected behaviour, I was hoping there would at least be some useful information in the notification itself so I could tell apart whether the change was triggered by an on-device action or synced from CloudKit.

Is there something I'm doing wrong, or is there any other easy way to get a notification when a remote change comes in?

Persistence Controller

struct PersistenceController {
    static let shared = PersistenceController()
        
    let container: NSPersistentCloudKitContainer
    
    init() {
        container = NSPersistentCloudKitContainer(name: "MyContainerName")
        
        let storeDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
        
        // Local
        let localUrl = storeDirectory.appendingPathComponent("local.store")
        let local = NSPersistentStoreDescription(url: localUrl)
        local.configuration = "Local"
        
        // Cloud
        let cloudUrl = storeDirectory.appendingPathComponent("cloud.store")
        let cloud = NSPersistentStoreDescription(url: cloudUrl)
        cloud.configuration = "Cloud"
        cloud.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "my.container.identifier")
        cloud.setOption(true as NSNumber, forKey: "NSPersistentStoreRemoteChangeNotificationOptionKey")
        
        container.persistentStoreDescriptions = [local, cloud]
        
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        
    }    
}

App Delegate

class AppDelegate: NSObject, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        NotificationCenter.default.addObserver(
            self, selector: #selector(type(of: self).storeRemoteChange(_:)),
            name: .NSPersistentStoreRemoteChange,
            object: PersistenceController.shared.container.persistentStoreCoordinator
        )
        return true
    }
    
    @objc
    func storeRemoteChange(_ notification: Notification) {
        
        precondition(notification.name == NSNotification.Name.NSPersistentStoreRemoteChange)

        print(notification.userInfo)
        
    }
    
}

Output from print statement

Optional([AnyHashable("NSStoreUUID"): C2D121212-421B-003F-9819-956411111169, AnyHashable("storeURL"): file:///var/mobile/Containers/Data/Application/3234FH#-DACF-493I-AD24-143E4CDSFE2/Library/Application%20Support/cloud.store])

Solution

  • If this is expected behaviour, I was hoping there would at least be some useful information in the notification itself so I could tell apart whether the change was triggered by an on-device action or synced from CloudKit.

    Yes this is expected behaviour, because whether local on device or change from the cloud this is recorded as change in your store/database.

    So in order to process changes from the cloud you can subscribe for database changes, read here