You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 15:06:46