Swift只能防止NSKeyedUnarchiver.decodeObject崩溃吗?

sim*_*ons 34 ios nskeyedunarchiver swift

NSKeyedUnarchiver.decodeObjectSIGABRT如果原始类未知,将导致崩溃.唯一的解决办法我已经看到醒目从斯威夫特的早期历史这个问题的日期,使用Objective C(也预日期斯威夫特2的实施要求guard,throws,try&catch).我可以找出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
}
//...
Run Code Online (Sandbox Code Playgroud)

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

NSKeyedUnarchiver方法.unarchiveTopLevelObjectWithData.validateValue双方都有throws.是否有某些方法可以使用这些?我无法弄清楚如何开始尝试validateValue在这种情况下实施.这甚至是一条可能的路线吗?或者我应该寻找解决方案的其他方法之一?

或者有没有人知道替代Swift 2解决这个问题的唯一方法?我相信我感兴趣的关键可能是有权利的$classname- 但TBH我在试图弄清楚如何实施方面是不是我的深度validateValue- 或者甚至是否是坚持下去的正确途径.我觉得我错过了一些明显的东西.


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

最初的答案为我解决了问题 - 即实施代表.

但是现在我已经找到了一个围绕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
}
//...
Run Code Online (Sandbox Code Playgroud)

rin*_*aro 27

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

例如,委托可以加载一些代码以将类引入运行时并返回类,或者替换不同的类对象.如果委托返回nil,则取消归档中止并且该方法会引发一个NSInvalidUnarchiveOperationException.

所以,你可以像这样实现委托:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

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

unarchiver.decodeObjectForKey("root")
// -> `nil` if the root object is unknown class.
Run Code Online (Sandbox Code Playgroud)

增加:

我没有注意到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?
}
Run Code Online (Sandbox Code Playgroud)

您可以:

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}
Run Code Online (Sandbox Code Playgroud)


ter*_*ina 18

另一种方法是修复用于NSCoding的类的名称.你只需要使用:

  • NSKeyedArchiver.setClassName("List", forClass: List.self 在序列化之前
  • NSKeyedUnarchiver.setClass(List.self, forClassName: "List") 在反序列化之前

在需要的地方.

看起来iOS扩展名在类名前面加上扩展名.

  • 这适用于新的应用程序,但是如果您的应用程序已经在商店中,请务必谨慎,因为在升级后的第一次启动时,任何现有数据都不会使用新的类名称进行存档,从而导致崩溃.确保您有一些代码以实现向后兼容性.一种方法是仍然使用委托返回正确的类名. (3认同)