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