Core data and cloudkit sync wwdc 2019 not working for beta 3
Asked Answered
J

5

6

I am trying to replicate the result of WWDC talk on syncing core data with cloud kit automatically.

I tried three approaches:

  1. Making a new master slave view app and following the steps at in wwdc 2019 talk, in this case no syncing happens

  2. Downloading the sample wwdc 2019 app also in this case no symcing happens

  3. I made a small app with a small core data and a cloud kit container in this case syncing happens but I have to restart the app. I suspected it had to do with history management so observed the NSPersistentStoreRemoteChange notification not nothing receives.

Appreciate any help.

Joinville answered 6/7, 2019 at 12:14 Comment(0)
F
9

I also played around with CoreData and iCloud and it work perfectly. I would like to list some important points that may help you go further:

  • You have to run the app on a real device with iCloud Acc We can now test iCloud Sync on Simulator, but it will not get notification automatically. We have to trigger manually by select Debug > Trigger iCloud Sync
  • Make sure you added Push Notification and iCloud capability to your app. Make sure that you don't Dave issue with iCloud container (in this case, you will see red text on iCloud session in Xcode)
  • In order to refresh the view automatically, you need to add this line into your Core Data Stack: container.viewContext.automaticallyMergesChangesFromParent = true.

Code:

public lazy var persistentContainer: NSPersistentCloudKitContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
         */
        let container = NSPersistentCloudKitContainer(name: self.modelName)
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
  • When you add some data, normally you should see console log begin with CloudKit: CoreData+CloudKit: ..........
  • Sometimes the data is not synced immediately, in this case, I force close the app and build a new one, then the data get syncing.
  • There was one time, the data get synced after few hours :(
Fluxmeter answered 6/7, 2019 at 16:7 Comment(6)
Thanks! The only hint I havent tried yet is that I haven't put it on a device and have been testing it on simulator, as I don't have two ios devices with version 13. Appreciate your reply and when test on real devices I put a reply here. But you also mentioned that in your case also syncing sometimes doesn't happen “seamlessly”. I wonder if related to the beta version.Joinville
I tested on one device and one simulator, when pushing data on simulator nothing received on device both for my own app and apples sample demoJoinville
I don't thing that iCloud sync works on simulator at all. You can install it on device an see whether your data is being synced to iCloud dashboard. The you can delete the app and install it again, then the data on iCloud should be synced to your deviceFluxmeter
@Fluxmeter - don't forget about turning Background Modes (Remote Notifications) onExtensor
I found that in the CoreDataCloudKitDemo the auto sync works well like all you metioned, but the "storeRemoteChange" method never get called. Don't know why. It seems description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) is not working.Ravin
For me this doesn't work. Don't know the reason why.Seaborg
S
7

I found that the NSPersistentStoreRemoteChange notification is posted by the NSPersistentStoreCoordinator and not by the NSPersistentCloudKitContainer, so the following code solves the problem:

// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(
        self, selector: #selector(self.storeRemoteChange(_:)),
        name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
Salish answered 4/12, 2019 at 14:26 Comment(0)
B
4

Also ran into the issue with .NSPersistentStoreRemoteChange notification not being sent.

Code from Apples example:

// Observe Core Data remote change notifications.
        NotificationCenter.default.addObserver(
            self, selector: #selector(type(of: self).storeRemoteChange(_:)),
            name: .NSPersistentStoreRemoteChange, object: container)

Solution for me was to not set the container as object for the notification, but nil instead. Is it not used anyway and prevents the notification from being received:

// Observe Core Data remote change notifications.
        NotificationCenter.default.addObserver(
            self, selector: #selector(type(of: self).storeRemoteChange(_:)),
            name: .NSPersistentStoreRemoteChange, object: nil)

Update: As per this answer: https://mcmap.net/q/694490/-nspersistentstoreremotechangenotification-not-getting-fired The correct way would be to set container.persistentStoreCoordinator as object:

// Observe Core Data remote change notifications.
        NotificationCenter.default.addObserver(
            self, selector: #selector(type(of: self).storeRemoteChange(_:)),
            name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
Barberabarberry answered 4/11, 2019 at 15:33 Comment(3)
this change to 'nil' worked for me. I'm now working through the notifications to understand when real changes happen. There are quite a few notifications for non-events.Palaearctic
I think you are observing a false positive. This post suggests the opposite as the accepted answer #59159013Grandiloquent
Good point, did not try, but seems like a valid solution. Updated my answer with this.Barberabarberry
S
3

I understand this answer comes late and is not actually specific to the WWDC 19 SynchronizingALocalStoreToTheCloud Apple's sample project to which OP refers to, but I had syncing issues (not upon launch, when it synced fine, but only during the app being active but idle, which seems to be case 3 of the original question) in a project that uses Core Data + CloudKit with NSPersistentCloudKitContainer and I believe the same problems I had - and now apparently I have solved - might affect other Users reading this question in the future.

My app was built using Xcode's 11 Master-Detail template with Core Data + CloudKit from the start, so I had to do very little to have syncing work initially:

  1. Enable Remote Notifications Background Mode in Signing & Capabilities for my target;
  2. Add the iCloud capability for CloudKit;
  3. Select the container iCloud.com.domain.AppName
  4. Add viewContext.automaticallyMergesChangesFromParent = true

Basically, I followed Getting Started With NSPersistentCloudKitContainer by Andrew Bancroft and this was enough to have the MVP sync between devices (Catalina, iOS 13, iPadOS 13) not only upon launch, but also when the app was running and active (thanks to step 4 above) and another device edited/added/deleted an object.
Being the Xcode template, it did not have the additional customisations / advanced behaviours of WWDC 2019's sample project, but it actually accomplished the goal pretty well and I was satisfied, so I moved on to other parts of this app's development and stopped thinking about sync.

A few days ago, I noticed that the iOS/iPadOS app was now only syncing upon launch, and not while the app was active and idle on screen; on macOS the behaviour was slightly different, because a simple command-tab triggered sync when reactivating the app, but again, if the Mac app was frontmost, no syncing for changes coming from other devices.

I initially blamed a couple of modifications I did in the meantime:

  • In order to have the sqlite accessible in a Share Extension, I moved the container in an app group by subclassing NSPersistentCloudKitContainer;
  • I changed the capitalisation in the name of the app and, since I could not delete the CloudKit database, I created a new container named iCloud.com.domain.AppnameApp (CloudKit is case insensitive, apparently, and yes, I should really start to care less about such things).

While I was pretty sure that I saw syncing work as well as before after each one of these changes, having sync (apparently) suddenly break convinced me, for at least a few hours, that either one of those modification from the default path caused the notifications to stop being received while the app was active, and that then the merge would only happen upon launch as I was seeing because the running app was not made aware of changes.

I should mention, because this could help others in my situation, that I was sure notifications were triggered upon Core Data context saves because CloudKit Dashboard was showing the notifications being sent:

CloudKit Dashboard historical push logs

So, I tried a few times clearing Derived Data (one never knows), deleting the apps on all devices and resetting the Development Environment in CloudKit's Dashboard (something I already did periodically during development), but I still had the issue of the notifications not being received.

Finally, I realised that resetting the CloudKit environment and deleting the apps was indeed useful (and I actually rebooted everything just to be safe ;) but I also needed to delete the app data from iCloud (from iCloud's Settings screen on the last iOS device where the app was still installed, after deleting from the others) if I really wanted a clean slate; otherwise, my somewhat messed up database would sync back to the newly installed app.

And indeed, a truly clean slate with a fresh Development Environment, newly installed apps and rebooted devices resumed the notifications being detected from the devices also when the apps are frontmost.
So, if you feel your setup is correct and have already read enough times that viewContext.automaticallyMergesChangesFromParent = true is the only thing you need, but still can't see changes come from other devices, don't exclude that something could have been messed up beyond your control (don't get me wrong: I'm 100% sure that it must have been something that I did!) and try to have a fresh start... it might seem obscure, but what isn't with the syncing method we are choosing for our app?

Stentor answered 21/6, 2020 at 16:27 Comment(1)
A short update to this in case these (great) instructions from @cfd1982 don't work for you. If you are setting the NSPersistentStoreDescription to interact with the public database rather than the private database (previous default before iOS 14 I believe) via description.cloudKitContainerOptions?.databaseScope = .public, it will only poll for changes from cloudkit every 30 minutes by design (see 10:30 into the WWDC 2020 session video on this topic: developer.apple.com/videos/play/wwdc2020/10650)Ellene
Y
2

I had the same problem, reason was that iCloudDrive must be enabled in your devices. Check it in the Settings of every your device

Yarvis answered 14/3, 2020 at 11:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.