swift Swift - CloudKit错误助手功能和扩展

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了swift Swift - CloudKit错误助手功能和扩展相关的知识,希望对你有一定的参考价值。

01. See:https://stackoverflow.com/questions/43553969/handling-cloudkit-errors
02. See: https://www.whatmatrix.com/blog/a-guide-to-cloudkit-how-to-sync-user-data-across-ios-devices/
03. See CloudKit error codes: https://developer.apple.com/documentation/cloudkit/ckerrorcode
04. https://stackoverflow.com/questions/44023936/cloudkit-full-and-complete-error-handling-example

01. Helper function to manage CloudKit Errors

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)
    }
}

02. Helper extension (partial example) to manage CloudKit Errors

import CloudKit

extension CKError {
	public func isRecordNotFound() -> Bool {
		return isZoneNotFound() || isUnknownItem()
	}
	public func isZoneNotFound() -> Bool {
		return isSpecificErrorCode(code: .zoneNotFound)
	}
	public func isUnknownItem() -> Bool {
		return isSpecificErrorCode(code: .unknownItem)
	}
	public func isConflict() -> Bool {
		return isSpecificErrorCode(code: .serverRecordChanged)
	}
	public func isSpecificErrorCode(code: CKError.Code) -> Bool {
		var match = false
		if self.code == code {
			match = true
		}
		else if self.code == .partialFailure {
			// This is a multiple-issue error. Check the underlying array
			// of errors to see if it contains a match for the error in question.
			guard let errors = partialErrorsByItemID else {
				return false
			}
			for (_, error) in errors {
				if let cke = error as? CKError {
					if cke.code == code {
						match = true
						break
					}
				}
			}
		}
		return match
	}
	// ServerRecordChanged errors contain the CKRecord information
	// for the change that failed, allowing the client to decide
	// upon the best course of action in performing a merge.
	public func getMergeRecords() -> (CKRecord?, CKRecord?) {
		if code == .serverRecordChanged {
			// This is the direct case of a simple serverRecordChanged Error.
			return (clientRecord, serverRecord)
		}
		guard code == .partialFailure else {
			return (nil, nil)
		}
		guard let errors = partialErrorsByItemID else {
			return (nil, nil)
		}
		for (_, error) in errors {
			if let cke = error as? CKError {
				if cke.code == .serverRecordChanged {
		// This is the case of a serverRecordChanged Error 
		// contained within a multi-error PartialFailure Error.
					return cke.getMergeRecords()
				}
			}
		}
		return (nil, nil)
	}
}

04. Full 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
}

以上是关于swift Swift - CloudKit错误助手功能和扩展的主要内容,如果未能解决你的问题,请参考以下文章

swift Swift - CloudKit - 获取用户ID

swift Swift - CloudKit - 推送通知

swift Swift - CloudKit共享

swift Swift - CloudKit - 维护本地缓存

swift Swift - CloudKit - 部署架构

swift Swift - CloudKit订阅 - 5