How to get notified on CKError.quotaExceeded

Hi all,

I have an iOS app which uses CloudKit and the standard NSPersistentCloudKitContainer, which I rely on for syncing app data between the user's devices. If the user's iCloud account is full I can see a log message while debugging in Xcode shortly after startup which looks something like this:

error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _requestAbortedNotInitialized:](2183): <NSCloudKitMirroringDelegate: 0x281ddc1e0> - Never successfully initialized and cannot execute request '<NSCloudKitMirroringExportRequest: 0x2841e00f0> 51383346-87BA-44D8-B527-A0B1EE35A0EF' due to error: <CKError 0x282c50db0: "Partial Failure" (2/1011); "Failed to modify some records"; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; container ID = "iCloud.com.neufsters.pangram"; partial errors: {
    E30B2972-FD4B-4D2A-BD1C-EB6F33F5367D:(com.apple.coredata.cloudkit.zone:__defaultOwner__) = <CKError 0x282c155f0: "Quota Exceeded" (25/2035); server message = "Quota exceeded"; op = FC4D3188D0A46ABC; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; Retry after 315.0 seconds>
    2FC9A487-D630-444D-B7F4-27A0F3A6B46E:(com.apple.coredata.cloudkit.zone:__defaultOwner__) = <CKError 0x282c52820: "Quota Exceeded" (25/2035); server message = "Quota exceeded"; op = FC4D3188D0A46ABC; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; Retry after 315.0 seconds>
    903DD6A0-0BD8-46C0-84FB-E89797514D9F:(com.apple.coredata.cloudkit.zone:__defaultOwner__) = <CKError 0x282c513e0: "Quota Exceeded" (25/2035); server message = "Quota exceeded"; op = FC4D3188D0A46ABC; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; Retry after 315.0 seconds>
}>

I would like to know how I can get a callback of some sort so I can run code if this CloudKit/CoreData error happens. In particular I'd like to put up some sort of warning to the user letting them know their data isn't going to sync.

Please note that I'm not looking for how to do error handling as a result of a user-initiated CloudKit API call. I'm looking for how to get notified when the background syncing logs errors like the above.

Thanks,

Russ

We would expect you to use NSPersistentCloudKitContainerEvent for this. You would observe event change notifications and see that the event failed with an error.

Thank you for your reply. I have tried something like the following, however the "quotaExceeded" never gets printed even though the above error message does. I can see also that the code enters the .partialFailure section of the if, however ckerror.partialErrorsByItemID is always nil or an empty list. Any suggests on how to unpack the error and get to the underlying .quotaExceeded?

class SyncMonitor {
    /// Where we store Combine cancellables for publishers we're listening to, e.g. NSPersistentCloudKitContainer's notifications.
    fileprivate var disposables = Set<AnyCancellable>()

    init() {
        NotificationCenter.default.publisher(for: NSPersistentCloudKitContainer.eventChangedNotification)
            .sink(receiveValue: { notification in
                
                print("notification: \(notification)")
                
                if let cloudEvent = notification.userInfo?[NSPersistentCloudKitContainer.eventNotificationUserInfoKey]
                    as? NSPersistentCloudKitContainer.Event {
                    // NSPersistentCloudKitContainer sends a notification when an event starts, and another when it
                    // ends. If it has an endDate, it means the event finished.
                    if cloudEvent.endDate == nil {
                        print("Starting an event...") // You could check the type, but I'm trying to keep this brief.
                    } else {
                        switch cloudEvent.type {
                        case .setup:
                            print("Setup finished!")
                        case .import:
                            print("An import finished!")
                        case .export:
                            print("An export finished!")
                        @unknown default:
                            assertionFailure("NSPersistentCloudKitContainer added a new event type.")
                        }

                        if cloudEvent.succeeded {
                            print("And it succeeded!")
                        } else {
                            print("But it failed!")
                        }

                        if let error = cloudEvent.error {
                            print("Error: \(error.localizedDescription)")
                            
                            guard let ckerror = error as? CKError else {
                                return
                            }

                            print("Error: code: \(ckerror.code), \(ckerror.localizedDescription)")
                            if ckerror.code == .partialFailure {
                                guard let errors = ckerror.partialErrorsByItemID else {
                                    return
                                }
                            
                                for (_, error) in errors {
                                    if let currentError = error as? CKError {
                                        print(currentError.localizedDescription)
                                    }
                                }
                            } else if ckerror.code == .quotaExceeded {
                                print("quotaExceeded")
                            }
                        }
                    }
                }
            })
            .store(in: &disposables)
    }
}
How to get notified on CKError.quotaExceeded
 
 
Q