CloudKit Error Handling

I am looking for general recommendations for handling CloudKit errors in Swift, and I am having trouble finding good examples on the Internet. Here is what interests me:

1) Do I have to consider every single type of error every time a possibility of an error arises or is it really not necessary?

2) I read that one of the common CloudKit error handling methods is to retry the operation after the time interval provided by the error message. Should this retry be my standard procedure for all errors?

3) Do different CloudKit operations (save, select, etc.) produce different types of errors or is there one standard set of CloudKit errors?

Thanks in advance! I'm just looking for general information on how to handle error handling with CloudKit, because I'm not sure where to start.

+2
source share
2 answers

I wrote a CloudKit reference that makes error handling easier. This is just a starting point, and much more can be done.

The main focus of this assistant in its current state is to simplify the repetition of errors that should be repeated after the appropriate timeout.

, , , iCloud. , , . , , . CloudKit .

. . , . .

import Foundation
import CloudKit

public class CloudKitHelper {
    private static func determineRetry(error: Error) -> Double? {
        if let ckerror = error as? CKError {
            switch ckerror {
            case CKError.requestRateLimited, CKError.serviceUnavailable, CKError.zoneBusy, CKError.networkFailure:
                let retry = ckerror.retryAfterSeconds ?? 3.0

                return retry
            default:
                return nil
            }
        } else {
            let nserror = error as NSError
            if nserror.domain == NSCocoaErrorDomain {
                if nserror.code == 4097 {
                    print("cloudd is dead")

                    return 6.0
                }
            }

            print("Unexpected error: \(error)")
        }

        return nil
    }

    public static func modifyRecordZonesOperation(database: CKDatabase, recordZonesToSave: [CKRecordZone]?, recordZoneIDsToDelete: [CKRecordZoneID]?, modifyRecordZonesCompletionBlock: @escaping (([CKRecordZone]?, [CKRecordZoneID]?, Error?) -> Void)) {
        let op = CKModifyRecordZonesOperation(recordZonesToSave: recordZonesToSave, recordZoneIDsToDelete: recordZoneIDsToDelete)
        op.modifyRecordZonesCompletionBlock = { (savedRecordZones: [CKRecordZone]?, deletedRecordZoneIDs: [CKRecordZoneID]?, error: Error?) -> Void in
            if let error = error {
                if let delay = determineRetry(error: error) {
                    DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
                        CloudKitHelper.modifyRecordZonesOperation(database: database, recordZonesToSave: recordZonesToSave, recordZoneIDsToDelete: recordZoneIDsToDelete, modifyRecordZonesCompletionBlock: modifyRecordZonesCompletionBlock)
                    }
                } else {
                    modifyRecordZonesCompletionBlock(savedRecordZones, deletedRecordZoneIDs, error)
                }
            } else {
                modifyRecordZonesCompletionBlock(savedRecordZones, deletedRecordZoneIDs, error)
            }
        }
        database.add(op)
    }

    public static func modifyRecords(database: CKDatabase, records: [CKRecord], completion: @escaping (([CKRecord]?, Error?) -> Void)) {
        CloudKitHelper.modifyAndDeleteRecords(database: database, records: records, recordIDs: nil) { (savedRecords, deletedRecords, error) in
            completion(savedRecords, error)
        }
    }

    public static func deleteRecords(database: CKDatabase, recordIDs: [CKRecordID], completion: @escaping (([CKRecordID]?, Error?) -> Void)) {
        CloudKitHelper.modifyAndDeleteRecords(database: database, records: nil, recordIDs: recordIDs) { (savedRecords, deletedRecords, error) in
            completion(deletedRecords, error)
        }
    }

    public static func modifyAndDeleteRecords(database: CKDatabase, records: [CKRecord]?, recordIDs: [CKRecordID]?, completion: @escaping (([CKRecord]?, [CKRecordID]?, Error?) -> Void)) {
        let op = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: recordIDs)
        op.savePolicy = .allKeys
        op.modifyRecordsCompletionBlock = { (savedRecords: [CKRecord]?, deletedRecordIDs: [CKRecordID]?, error: Error?) -> Void in
            if let error = error {
                if let delay = determineRetry(error: error) {
                    DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
                        CloudKitHelper.modifyAndDeleteRecords(database: database, records: records, recordIDs: recordIDs, completion: completion)
                    }
                } else {
                    completion(savedRecords, deletedRecordIDs, error)
                }
            } else {
                completion(savedRecords, deletedRecordIDs, error)
            }
        }
        database.add(op)
    }
}
+4

, . Apple WWDC, .

, , . , . , , , . , , , , , , , .

, CKError.h. CKError. , CKErrorPartialFailure, , , . IOS 10, CKError.h :

typedef NS_ENUM(NSInteger, CKErrorCode) {
    CKErrorInternalError                  = 1,  /* CloudKit.framework encountered an error.  This is a non-recoverable error. */
    CKErrorPartialFailure                 = 2,  /* Some items failed, but the operation succeeded overall. Check CKPartialErrorsByItemIDKey in the userInfo dictionary for more details. */
    CKErrorNetworkUnavailable             = 3,  /* Network not available */
    CKErrorNetworkFailure                 = 4,  /* Network error (available but CFNetwork gave us an error) */
    CKErrorBadContainer                   = 5,  /* Un-provisioned or unauthorized container. Try provisioning the container before retrying the operation. */
    CKErrorServiceUnavailable             = 6,  /* Service unavailable */
    CKErrorRequestRateLimited             = 7,  /* Client is being rate limited */
    CKErrorMissingEntitlement             = 8,  /* Missing entitlement */
    CKErrorNotAuthenticated               = 9,  /* Not authenticated (writing without being logged in, no user record) */
    CKErrorPermissionFailure              = 10, /* Access failure (save, fetch, or shareAccept) */
    CKErrorUnknownItem                    = 11, /* Record does not exist */
    CKErrorInvalidArguments               = 12, /* Bad client request (bad record graph, malformed predicate) */
    CKErrorResultsTruncated NS_DEPRECATED(10_10, 10_12, 8_0, 10_0, "Will not be returned") = 13,
    CKErrorServerRecordChanged            = 14, /* The record was rejected because the version on the server was different */
    CKErrorServerRejectedRequest          = 15, /* The server rejected this request.  This is a non-recoverable error */
    CKErrorAssetFileNotFound              = 16, /* Asset file was not found */
    CKErrorAssetFileModified              = 17, /* Asset file content was modified while being saved */
    CKErrorIncompatibleVersion            = 18, /* App version is less than the minimum allowed version */
    CKErrorConstraintViolation            = 19, /* The server rejected the request because there was a conflict with a unique field. */
    CKErrorOperationCancelled             = 20, /* A CKOperation was explicitly cancelled */
    CKErrorChangeTokenExpired             = 21, /* The previousServerChangeToken value is too old and the client must re-sync from scratch */
    CKErrorBatchRequestFailed             = 22, /* One of the items in this batch operation failed in a zone with atomic updates, so the entire batch was rejected. */
    CKErrorZoneBusy                       = 23, /* The server is too busy to handle this zone operation. Try the operation again in a few seconds. */
    CKErrorBadDatabase                    = 24, /* Operation could not be completed on the given database. Likely caused by attempting to modify zones in the public database. */
    CKErrorQuotaExceeded                  = 25, /* Saving a record would exceed quota */
    CKErrorZoneNotFound                   = 26, /* The specified zone does not exist on the server */
    CKErrorLimitExceeded                  = 27, /* The request to the server was too large. Retry this request as a smaller batch. */
    CKErrorUserDeletedZone                = 28, /* The user deleted this zone through the settings UI. Your client should either remove its local data or prompt the user before attempting to re-upload any data to this zone. */
    CKErrorTooManyParticipants            NS_AVAILABLE(10_12, 10_0) = 29, /* A share cannot be saved because there are too many participants attached to the share */
    CKErrorAlreadyShared                  NS_AVAILABLE(10_12, 10_0) = 30, /* A record/share cannot be saved, doing so would cause a hierarchy of records to exist in multiple shares */
    CKErrorReferenceViolation             NS_AVAILABLE(10_12, 10_0) = 31, /* The target of a record parent or share reference was not found */
    CKErrorManagedAccountRestricted       NS_AVAILABLE(10_12, 10_0) = 32, /* Request was rejected due to a managed account restriction */
    CKErrorParticipantMayNeedVerification NS_AVAILABLE(10_12, 10_0) = 33, /* Share Metadata cannot be determined, because the user is not a member of the share.  There are invited participants on the share with email addresses or phone numbers not associated with any iCloud account. The user may be able to join the share if they can associate one of those email addresses or phone numbers with their iCloud account via the system Share Accept UI. Call UIApplication openURL on this share URL to have the user attempt to verify their information. */
} NS_ENUM_AVAILABLE(10_10, 8_0);

, , , NSAssert, . , , , , . , , .

+3

Source: https://habr.com/ru/post/1677350/


All Articles