iOS应用中需检查Error协议变量转NSError?CloudKit示例解析
CloudKit错误处理函数解析:
handleCloudKitError的类型检查与返回nil原因 1. guard let nsError = error as NSError?类型检查的目的
Swift中所有遵循Error协议的类型都可桥接为NSError,这个检查的核心作用有两点:
- 获取Cocoa错误体系的核心属性:
NSError提供了code、domain、userInfo这些CloudKit错误处理必需的字段,后续代码需要通过userInfo读取CKPartialErrorsByItemIDKey处理部分错误,弹窗提示也依赖code和domain。 - 快速过滤可忽略错误:如果转换失败,说明传入的
error不是Cocoa体系内的错误(比如非桥接的自定义Error,或error本身为nil),按照函数规则直接返回nil,代表无错误或错误可忽略。
2. 检查转换失败的原因
可通过以下步骤排查:
- 先确认
error是否为nil:如果error本身是nil,转换自然失败,对应无错误的正常流程。 - 若
error非nil,打印具体信息:执行print(error!)和print(type(of: error!)),查看错误的实际类型和描述,判断是否为未遵循CustomNSError协议的自定义Error,导致无法桥接为NSError。 - 调试断点分析:在转换代码处添加断点,查看
error的内存结构,确认是否包含NSError所需的domain、code等属性。
handleCloudKitError返回nil的8种可能原因
- 符合Error协议的error参数无法转换为NSError;
- NSError.userInfo中无CKPartialErrorsByItemIDKey对应值,或值无法转为NSDictionary;
- 未传入affectedObjects参数;
- affectedObjects与CKPartialErrorsByItemIDKey对应值无共同可转为CKError的项;
- operation参数不是handlePartialError中检查的
.deleteZones、.deleteRecords或.deleteSubscriptions; - CKError.code为
.unknownItem(表示指定记录不存在); - 不满足operation为
.fetchChanges且CKError.code为.changeTokenExpired或.zoneNotFound; - error转为NSError后无法再转为CKError。
完整相关代码
// Return nil: No error or the error is ignorable. // Return a CKError: There's an error. The caller needs to determine how to handle it. // func handleCloudKitError(_ error: Error?, operation: CloudKitOperationType, affectedObjects: [Any]? = nil, alert: Bool = false) -> CKError? { // nsError == nil: Everything goes well and the caller can continue. // guard let nsError = error as NSError? else { return nil } // Partial errors can happen when fetching or changing the database. // // When modifying zones, records, and subscriptions, .serverRecordChanged may happen if // the other peer changes the item at the same time. In that case, retrieve the first // CKError object and return to the caller. // // In the case of .fetchRecords and fetchChanges, the other peer might delete the specified // items or zones, so they might not exist in the database (.unknownItem or .zoneNotFound). // if let partialError = nsError.userInfo[CKPartialErrorsByItemIDKey] as? NSDictionary { // If the error doesn't affect the affectedObjects, ignore it. // If it does, only handle the first error. // let errors = affectedObjects?.map({ partialError[$0] }).filter({ $0 != nil }) guard let ckError = errors?.first as? CKError else { return nil } return handlePartialError(ckError, operation: operation, alert: alert) } // In the case of fetching changes: // .changeTokenExpired: Return for callers to refetch with a nil server token. // .zoneNotFound: Return for callers to switch zones because the current zone doesn't exist. // .partialFailure: zoneNotFound triggers a partial error as well. // if operation == .fetchChanges { if let ckError = error as? CKError { if ckError.code == .changeTokenExpired || ckError.code == .zoneNotFound { return ckError } } } // If the client requires an alert, alert the error, or append the error message to an existing alert. // if alert { alertError(code: nsError.code, domain: nsError.domain, message: nsError.localizedDescription, operation: operation) } print("\(operation.rawValue) operation error: \(nsError)") return error as? CKError } private func handlePartialError(_ error: CKError, operation: CloudKitOperationType, alert: Bool = false) -> CKError? { // Items don't exist. Silently ignore the error for the .delete... operation. // if operation == .deleteZones || operation == .deleteRecords || operation == .deleteSubscriptions { if error.code == .unknownItem { return nil } } if error.code == .serverRecordChanged { print("Server record changed. Consider using serverRecord and ignore this error!") } else if error.code == .zoneNotFound { print("Zone not found. May have been deleted. Probably ignore!") } else if error.code == .unknownItem { print("Unknown item. May have been deleted. Probably ignore!") } else if error.code == .batchRequestFailed { print("Atomic failure!") } else { if alert { alertError(code: error.errorCode, domain: CKError.errorDomain, message: error.localizedDescription, operation: operation) } print("\(operation.rawValue) operation error: \(error)") } return error } private func alertError(code: Int, domain: String, message: String, operation: CloudKitOperationType) { print("#", #function) DispatchQueue.main.async { guard let scene = UIApplication.shared.connectedScenes.first, let sceneDeleate = scene.delegate as? SceneDelegate, let viewController = sceneDeleate.window?.rootViewController else { return } let message = "\(operation.rawValue) operation hit error.\n" + "Error code: \(code)\n" + "Domain: \(domain)\n" + message if let existingAlert = viewController.presentedViewController as? UIAlertController { print("message:", existingAlert.message) existingAlert.message = (existingAlert.message ?? "") + "\n\n\(message)" return } let newAlert = UIAlertController(title: "CloudKit Operation Error!", message: message, preferredStyle: .alert) newAlert.addAction(UIAlertAction(title: "OK", style: .default)) print("message:", message) viewController.present(newAlert, animated: true) } }
内容的提问来源于stack exchange,提问作者daniel
相关产品推荐
相关产品推荐

