6

Apple are very clear, in every video they state that "The difference between a CloudKit app which handles errors and one that doesn't is the difference between a working app and one that doesn't". However, I can't find anywhere a proper list of what each error means, which Operations throw what or any example code of CloudKit error handling done well for various CKOperations. Even worse, many examples don't handle errors at all and I can't find any documentation from Apple either.

Does anyone have a full and complete example they can share? Any lists of which Operations can throw what?

This post shows a list of errors and a short description of each error. I created this new post because I'm specifically looking for full and complete examples of error handling as Apple recommends. The other post has an incomplete example and is asking specific questions. I already highlighted this post in the comments because it contains a short description of each error type which is useful.

Community
  • 1
  • 1
robwithhair
  • 350
  • 3
  • 11
  • I've found [this post](http://stackoverflow.com/a/43575025/3433061) here which contains a short description for each error. – robwithhair May 17 '17 at 11:47
  • I created this new post because I'm specifically looking for full and complete examples of error handling as Apple recommends. The post you mention has an incomplete example and is asking specific questions. I already highlighted this post above because it contains a short description of each error type which is useful. – robwithhair May 17 '17 at 12:26
  • https://developer.apple.com/reference/cloudkit/ckerrorcode – Papershine May 17 '17 at 12:32
  • Rob, I'm in the same boat now myself. I'm just at the point where my basic CloudKit syncing system is solid and fast, and I need to fill in all the error conditions. In going through the Apple CloudKit code examples I ran across their solution in the "CloudKit Share" sample code. I was thinking I would use this as the foundation of my solution... Not sure if it shows all possible errors, but it looks like a good start... https://developer.apple.com/library/content/samplecode/CloudKitShare/Introduction/Intro.html – Brian M Mar 12 '18 at 18:06

1 Answers1

17

Rob,

Here an example...

func files_saveNotes(rex: Int) {
     var localChanges:[CKRecord] = []

        let newRecordID = CKRecordID(recordName: sharedDataAccess.returnRexID(index2seek: rex))
        let newRecord = CKRecord(recordType: "Note", recordID: newRecordID)

        let theLinkID = CKReference(recordID: sharedDataAccess.iCloudID, action: .deleteSelf)
        let thePath = sharedDataAccess.fnGet(index2seek: rex)
        newRecord["theLink"] = theLinkID
        newRecord["theNo"] = rex as CKRecordValue?
        newRecord["thePath"] = thePath as CKRecordValue?



        let miam = sharedDataAccess.fnGetText(index2seek: rex)
        let range = NSRange(location: 0, length: miam.length)
        let dataMiam = try? miam.data(from: range, documentAttributes: [NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType])

        newRecord["theRTF"] = dataMiam as? CKRecordValue


    localChanges.append(newRecord)
    let records2Erase:[CKRecordID] = []

    let saveRecordsOperation = CKModifyRecordsOperation(recordsToSave: localChanges, recordIDsToDelete: records2Erase)
    saveRecordsOperation.savePolicy = .allKeys
    saveRecordsOperation.perRecordCompletionBlock =  { record, error in
        if error != nil {
            //print(error!.localizedDescription)
        }
        // deal with conflicts
        // set completionHandler of wrapper operation if it's the case
    }
    saveRecordsOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
        self.theApp.isNetworkActivityIndicatorVisible = false

        guard error == nil else {
            if let ckerror = error as? CKError {
                if ckerror.code == CKError.requestRateLimited {
                    let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                    DispatchQueue.main.async {
                        Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveNotes), userInfo: nil, repeats: false)
                    }
                } else if ckerror.code == CKError.zoneBusy {
                    let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                    DispatchQueue.main.async {
                        Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveNotes), userInfo: nil, repeats: false)
                    }
                } else if ckerror.code == CKError.limitExceeded {
                    let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                    DispatchQueue.main.async {
                        Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveNotes), userInfo: nil, repeats: false)
                    }
                } else if ckerror.code == CKError.notAuthenticated {
                    NotificationCenter.default.post(name: Notification.Name("noCloud"), object: nil, userInfo: nil)
                } else if ckerror.code == CKError.networkFailure {
                    NotificationCenter.default.post(name: Notification.Name("networkFailure"), object: nil, userInfo: nil)
                } else if ckerror.code == CKError.networkUnavailable {
                    NotificationCenter.default.post(name: Notification.Name("noWiFi"), object: nil, userInfo: nil)
                } else if ckerror.code == CKError.quotaExceeded {
                    NotificationCenter.default.post(name: Notification.Name("quotaExceeded"), object: nil, userInfo: nil)
                } else if ckerror.code == CKError.partialFailure {
                    NotificationCenter.default.post(name: Notification.Name("partialFailure"), object: nil, userInfo: nil)
                } else if (ckerror.code == CKError.internalError || ckerror.code == CKError.serviceUnavailable) {
                    NotificationCenter.default.post(name: Notification.Name("serviceUnavailable"), object: nil, userInfo: nil)
                }
            } // end of guard statement
            return
        }
        if error != nil {
            //print(error!.localizedDescription)
        } else {
            //
        }
    }

    saveRecordsOperation.qualityOfService = .background
    privateDB.add(saveRecordsOperation)
    theApp.isNetworkActivityIndicatorVisible = true
}
user3069232
  • 8,587
  • 7
  • 46
  • 87
  • Thanks for this! Really great idea to use NotificationCenter to display a suitable response to the user. I was considering an exhaustive switch would ensure that all errors were handled and future additions to errors would provide compiler error to inform the user? However as not all CKOperations can throw all CKError types this seems many of the errors would not be necessary. It's a shame there's not some list of which operations throw what errors. At least I've not found one. – robwithhair May 18 '17 at 10:02
  • Note that the error only contains the CKErrorRetryAfterKey in two cases: serviceUnavailable and requestRateLimited (at least per apple documentation: https://developer.apple.com/documentation/cloudkit/ckerrorretryafterkey) – downstroy Dec 17 '21 at 08:51