防止 NSKeyedArchiver 在没有 Objective-C 的情况下抛出异常

Prevent NSKeyedArchiver throwing exception without Objective-C

在低于 11 的 iOS 版本上,抛出 archivedData(withRootObject:requiringSecureCoding:) 不可用,因此我尝试在低于 iOS 11 的版本上执行等效操作:

let archiveData: NSData
if #available(iOS 11.0, *) {
    archiveData = try NSKeyedArchiver.archivedData(
        withRootObject: rootObject,
        requiringSecureCoding: true
    ) as NSData
} else {
    NSKeyedArchiver.archivedData(withRootObject: userActivity)
    let mutableData = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWith: mutableData)
    archiver.requiresSecureCoding = true
    archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
    if let error = archiver.error {
        throw error
    }
    archiver.finishEncoding()
    archiveData = mutableData
}

但是,当 rootObjectencode(with:) 函数中调用 NSCoder.failWithError(_:) 时,会引发 NSInvalidUnarchiveOperationException 异常。

如果我这样继承 NSKeyedArchiver

final class KeyedArchiver: NSKeyedArchiver {

    override var decodingFailurePolicy: NSCoder.DecodingFailurePolicy {
        return .setErrorAndReturn
    }

}

它引发了一个 NSInternalInconsistencyException 异常消息 Attempting to set decode error on throwing NSCoder

有没有办法在不抛出异常的情况下进行这种归档,而不是编写一个 Objective-C 函数来捕获异常并将其作为错误抛出?

您在编码时仍然遇到异常的原因是 .decodingFailurePolicy 仅在解码时有效(即,通过 NSKeyedUnarchiver 解压缩),而不是编码。任何在编码上调用 .failWithError(_:) 的对象仍会产生异常。

在编码时调用 .failWithError(_:) 相对较少:通常,一旦您在运行时拥有一个完全构建的对象,它就不太可能处于不可编码的状态。当然在某些情况下这是可能的,所以你真的有两个选择:

  1. 如果您正在处理您知道的对象并且可以提前检查它们是否处于有效状态以进行编码,您应该这样做并避免对无效对象进行编码
  2. 如果您正在使用无法预先验证的任意对象,您将不得不通过 Objective-C 函数将标注包装到 NSKeyedArchiver,该函数可以捕获异常(理想情况下 throw 包含该异常的 Error,就像较新的 NSKeyedArchiver API 代表您所做的那样)

根据您上面的评论,选项 2 是您最好的选择。


顺便说一句,您可以缩短后备代码以避免必须构建中间 NSMutableData 实例:

let archiver = NSKeyedArchiver()
archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
archiveData = archiver.encodedData

NSKeyedArchiver 上的默认初始化器构造了一个新的存档器,其中包含要使用的内部可变数据实例,NSKeyedArchiver.encodedData 属性 会代表您自动调用 -finishEncoding