ggruen / CloudKitSyncMonitor

Monitor current state of NSPersistentCloudKitContainer sync
MIT License
465 stars 43 forks source link

Status stuck on “Syncing” in iOS 17 #19

Closed infinitepower18 closed 11 months ago

infinitepower18 commented 1 year ago

Hi, thanks for this package.

I have implemented CloudKit in my app and it’s been working perfectly in iOS 16. However in iOS 17 (Release Candidate) CloudKit sync has been very unreliable, mostly after reinstallation of the app. The sync state is stuck in either “sync not started” or “syncing”. After about 30 minutes, iCloud sync suddenly works until the next time I uninstall and reinstall the app.

IMG_4766

I’m really confused as I haven’t faced any problems in iOS 16, only in iOS 17. Is it an iOS bug if it’s stuck on “sync not started” or “syncing” states for long periods of time?

Appreciate your input regarding this matter.

ggruen commented 1 year ago

Hi @infinitepower18,

Getting stuck on "sync not started" or "syncing" can either mean that NSPersistentCloudKitContainer isn't sending notifications or that it's not syncing; either way would be an iOS bug unless they've documented some kind of change in their notifications. The fact that it eventually starts working makes me think it's an iOS bug though.

NSPersistentCloudKitContainer does what it wants when it wants, and sometimes that means it doesn't sync. In the early days of NSPersistentCloudKitContainer, we'd see issues with it not syncing for 30 minutes or so pretty frequently. In fact, issues like that were what inspired me to write this package. Unfortunately, I don't have any suggestions to make it sync. CloudKitSyncMonitor just monitors NSPersistentCloudKitContainer so that we know what it's doing - it can't change when syncs happen.

Hopefully the delays are a bug in iOS 17 RC that they'll fix before iOS 17 is released.

infinitepower18 commented 1 year ago

Hi @ggruen, thanks for your response.

The last time I reinstalled the app on my iPhone was probably 2 days ago when the RC came out. After the initial sync issues that happen after installation cloudkit was working fine and status was showing Synced with iCloud. Today when I opened the app I noticed the status was stuck on syncing again. However after rebooting my phone, it fixed itself.

I really hope it’s an iOS bug. I posted on the dev forums as well and someone said it may be some bug with the signing and capabilities. https://developer.apple.com/forums/thread/737370

ggruen commented 1 year ago

Hi @infinitepower18 ,

I recommend testing with a couple of devices (or a device and icloud.com's developer dashboard) to see if your data is syncing when your status says it's not. If it is, then there could be a bug in the NSPersistentCloudKitContainer notifications, your app, or CloudKitSyncMonitor. But if it doesn't sync, then it's iOS's problem (and unfortunately your app's problem with little you can do about it except file a FB for Apple).

If you do test, please post your results here so others experiencing the issue can see (and we can tell if there's a possible issue in CloudKitSyncMonitor). I put CloudKitSyncMonitor last in the list of probable causes because it's a simple package, so there's not much that can break it except NSPersistentCloudKitContainer not firing the notifications it promises to.

infinitepower18 commented 1 year ago

The status shown by CloudKitSyncMonitor is accurate. When it actually doesn't sync, the status either shows Sync not started or Syncing. When CloudKit works, it shows Synced with iCloud.

I will do some more testing and will try to post results here. I have also filed a report with Apple FB13165914. Today I have implemented an Apple Watch app to go alongside the iOS app. My watch is on watchOS 10 RC and it seems to be syncing quite reliably. I haven't added the sync monitor to it yet but will do so tomorrow for troubleshooting purposes in case something goes wrong with it.

For reference, here is the code I use for CloudKit. I think when it doesn't sync I don't see anything logged in the CloudKit console. The same code is used for both iOS app and watch app.

import SwiftUI
import CoreData

class DataController {
    var container: NSPersistentCloudKitContainer
    @AppStorage(wrappedValue:true,"syncEnabled",store:UserDefaults(suiteName: "group.com.ip18.SubManager")) var syncEnabled
    static let shared = DataController()
    init() {
        container = NSPersistentCloudKitContainer(name: "Subscriptions")
        load(syncEnabled: syncEnabled)
    }

    func load(syncEnabled: Bool = true) {
        let url = URL.storeURL(for: "group.com.ip18.SubManager", databaseName: "Subscriptions")
        let storeDescription = NSPersistentStoreDescription(url:url)
        if syncEnabled {
            storeDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.ip18.SubManager")
        } else {
            storeDescription.cloudKitContainerOptions = nil
        }
        let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
        storeDescription.setOption(true as NSNumber, forKey: remoteChangeKey)
        storeDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        container.persistentStoreDescriptions = [storeDescription]
        /*
        // Only initialize the schema when building the app with the
        // Debug build configuration.
        #if DEBUG
        do {
            // Use the container to initialize the development schema.
            try container.initializeCloudKitSchema(options: [])
        } catch {
            // Handle any errors.
        }
        #endif
        */
        container.loadPersistentStores(completionHandler: {(storeDescription,error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}
infinitepower18 commented 11 months ago

Managed to solve it, it was because I had both my widget and app syncing to iCloud at the same time. Apple told me to have only the app or the widget handle the syncing. So I let the app handle the syncing and only have the widget read the local data store.

Will go ahead and close this issue now. I hope this helps someone.