ios - Swift 唯一防止 NSKeyedUnarchiver.decodeObject 崩溃的方法?

标签 ios swift nskeyedunarchiver

如果原始类未知,

NSKeyedUnarchiver.decodeObject 将导致崩溃/SIGABRT。我所看到的解决这个问题的唯一解决方案可以追溯到 Swift 的早期历史,并且需要使用 Objective C(也早于 Swift 2 对 guardthrows 的实现)尝试 & 捕捉)。我可以找出 Objective C 路线 - 但如果可能的话,我更愿意了解仅 Swift 的解决方案。

例如 - 数据已使用 NSPropertyListFormat.XMLFormat_v1_0 编码。如果编码数据的类未知,则以下代码将在 unarchiver.decodeObject() 处失败。

//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)

//it will crash after this if the class in the xml file is not known

if let newListCollection = (unarchiver.decodeObject()) as? List {
    return newListCollection
} else {
    return nil
}
//...

我正在寻找 Swift 2 唯一的方法来在尝试 .decodeObject 之前测试数据是否有效 - 因为 .decodeObject 没有 throws - 这意味着 try - catch 在 Swift 中似乎不是一个选项(没有 throws 的方法不能包装 AFAIK)。或者,另一种解码数据的方法会抛出一个错误,如果解码失败,我可以捕捉到。我希望用户能够从 iCloud 驱动器或 Dropbox 导入文件 - 因此需要对其进行适当验证。我不能假设编码数据是安全的。

NSKeyedUnarchiver 方法 .unarchiveTopLevelObjectWithData.validateValue 都有 throws。也许有什么方法可以使用这些?在这种情况下,我什至不知道如何开始尝试实现 validateValue。这甚至是一条可能的路线吗?或者我应该寻找其他方法之一来解决问题?

或者有人知道解决这个问题的另一种 Swift 2 唯一方法吗?我相信我感兴趣的关键可能是标题为 $classname - 但说实话我在尝试找出如何实现 validateValue 方面超出了我的深度 - 或者甚至这是否是坚持下去的正确途径。我觉得我遗漏了一些明显的东西。


编辑:这是一个解决方案 - 感谢下面 rintaro 的出色回答

最初的答案为我解决了这个问题——即实现委托(delegate)。

但是现在我已经采用了围绕 rintaro 的额外编辑响应构建的解决方案,如下所示:

//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)

do {
    let decodedDataObject = try unarchiver.decodeTopLevelObject()
    if let newListCollection = decodedDataObject as? List {
        return newListCollection
    } else {
        return nil
    }
}
catch {
    return nil
}
//...

最佳答案

NSKeyedUnarchiver 遇到未知类时,unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:)委托(delegate)方法被调用。

The delegate may, for example, load some code to introduce the class to the runtime and return the class, or substitute a different class object. If the delegate returns nil, unarchiving aborts and the method raises an NSInvalidUnarchiveOperationException.

因此,您可以像这样实现委托(delegate):

class MyUnArchiverDelegate: NSObject, NSKeyedUnarchiverDelegate {

    // This class is placeholder for unknown classes.
    // It will eventually be `nil` when decoded.
    final class Unknown: NSObject, NSCoding  {
        init?(coder aDecoder: NSCoder) { super.init(); return nil }
        func encodeWithCoder(aCoder: NSCoder) {}
    }

    func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
        return Unknown.self
    }
}

然后:

let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)
let delegate = MyUnArchiverDelegate()
unarchiver.delegate = delegate

unarchiver.decodeObjectForKey("root")
// -> `nil` if the root object is unknown class.

已添加:

我没有注意到 NSCoderextension 和更快捷的方法:

extension NSCoder {
    @warn_unused_result
    public func decodeObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) -> DecodedObjectType?
    @warn_unused_result
    @nonobjc public func decodeObjectOfClasses(classes: NSSet?, forKey key: String) -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObject() throws -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObjectForKey(key: String) throws -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) throws -> DecodedObjectType?
    @warn_unused_result
    public func decodeTopLevelObjectOfClasses(classes: NSSet?, forKey key: String) throws -> AnyObject?
}

您可以:

do {
    try unarchiver.decodeTopLevelObjectForKey("root")
    // OR `unarchiver.decodeTopLevelObject()` depends on how you archived.
}
catch let (err) {
    print(err)
}
// -> emits something like:
// Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked}

关于ios - Swift 唯一防止 NSKeyedUnarchiver.decodeObject 崩溃的方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32904811/

相关文章:

iphone - 推沃什 : Swift Code

ios - 仅更改我的应用程序的音量

ios - 将 View 推到标签栏 Controller 中的导航 Controller 上不会产生动画

ios - 使用 NSKeyedUnarchiver unarchivedObject(ofClass :from:)) 取消归档数组

ios - iOS Swift 中的碰撞检测

ios - 当前模态视图 Controller 完成滑动后如何制作动画?

ios - 通过蓝牙发送信息

ios - 禁用重拍、播放和使用视频画面 UIImagePickerController

swift - 如何 NSKeyedUnarchiver.unarchiveObject

swift - NSKeyedUnarchiver 不适用于 Swift 3