ggruen / CloudKitSyncMonitor

Monitor current state of NSPersistentCloudKitContainer sync
MIT License
486 stars 44 forks source link

Just a few questions #17

Closed thatvirtualboy closed 1 year ago

thatvirtualboy commented 1 year ago

Hi and thanks so much for creating this useful tool! I had a view questions I'm hoping you can help me with:

  1. Can you explain a bit more what requirements need to happen in order for "Setup State" to show successful? I ask because I had re-installed my app, successfully re-download all data from iCloud, Import/Export showed Successful but Setup was Not Started. It wasn't until I killed and re-launched that it updated.

  2. I have a user who has just over 100 saved objects on their iPhone in my app, but only 94 are syncing to their iPad. New projects aren't syncing between the two devices, and the status page is reading: Export Failed "CKERRORDOMAIN ERROR 2" -- I can't find much online about this error or what the suggested steps to fix it are. I'm not expecting you to help me troubleshoot, but I'm curious if you've come across this error before and if you've found a successful and repeatable way to address it.

Thanks so much!

ggruen commented 1 year ago

Hi @thatvirtualboy ,

  1. setupState is set to .succeeded(started: startDate, ended: endDate) when NSPersistentCloudKitContainer sends a SyncEvent whose type is .setup and whose startDate and endDate properties are not nil (which in theory means the setup has ended, therefore setup has succeeded). See internal func setProperties(from event: SyncEvent). If setupState is showing .notStarted, it means that CloudKitSyncMonitor didn't receive one or more events from NSPersistentCloudKitContainer. You might have a race condition in which NSPersistentCloudKitContainer is firing events before your app is detecting them with CloudKitSyncMonitor. (NSPersistentCloudKitContainer publishes a stream of events (of type SyncEvent) telling your app what it's doing. CloudKitSyncMonitor listens to those events and turns them into state variables you can check and display; if it misses events, it won't be able to update the variables). You may just need to make sure to set up CloudKitSyncMonitor before NSPersistentCloudKitContainer, although it's been so long since I've done that that I can't remember how.
  2. CKERRORDOMAIN ERROR 2 is a network error - the device can't connect to the network (or, really, it is running into some network-like error when CloudKit is trying to export data). Export errors like this are why I wrote CloudKitSyncMonitor, as iCloud is the "source of truth" for synced data, and the usual fix for errors like this, if it's not something simple like they're not on the Internet or a firewall is blocking outgoing traffic, is to delete and reinstall the app. Yes, that means the 6 items that are not exporting from the iPhone (assuming the iPhone is what's showing the export error) will be lost. I'll pause a moment while you gasp and go through the stages of grief. Your user might be able to go into Settings and turn iCloud Sync off and back on for your app, but I'm pretty sure the local data will be deleted anyway, not merged. With NSPersistentCloudKitContainer, it's critical that data make it up to iCloud, or it doesn't really exist. However, I also haven't run into random export errors like that since iOS 14 (15?) or so, so make sure your users are on new OSes. NSPersistentCloudKitContainer was very buggy at first, having frequent problems with items not syncing; detecting that export error quickly was critical to avoiding data loss. I assume your users are on something newer than iOS 14, but you never know, and it makes a big difference in stability.
thatvirtualboy commented 1 year ago

@ggruen Thanks Grant so much for the detailed explanation on both items! This is very helpful. Unfortunately the export errors are still present on current iOS builds, though seemingly less frequent.

At any rate, I really appreciate the reply. Thank you!

aehlke commented 8 months ago

You may just need to make sure to set up CloudKitSyncMonitor before NSPersistentCloudKitContainer,

Does this mean that instead of just having an ObservedObject in a view, the sync monitor should also be initialized somewhere in the app delegate before it's actually later used by a view that the user navigates to? The docs don't suggest triggering some behavior before its use in a view. Thanks

ggruen commented 8 months ago

Hi @aehlke,

Good question, and I think there may be a race condition in the design of the module in "normal" use.

Basically, CloudKitSyncMonitor is usually accessed as a singleton (SyncMonitor.shared), but the SyncMonitor instance won't be initialized until that singleton is accessed. So, if CloudKitSyncMonitor is used in a view (as will usually be the case), it's possible that the view won't be initialized before NSPersistentCloudKitContainer starts sending messages, so it could miss a message.

In my code, I use a view model, which is initialized very early in the startup process (I forget where and I've paused my Apple development for the time being so I can't look it up), so I didn't run into problems.

If there is an issue caused by missing early messages, then you can try putting let _ = SyncMonitor.shared before NSPersistentCloudKitContainer is set up (e.g. in the app delegate) and see if that fixes it. That should cause SyncMonitor to subscribe to the notifications before NSPersistentCloudKitContainer can start doing anything.

If you try this, please post your findings here. :)

aehlke commented 8 months ago

That's what I ended up doing, thanks!