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

时间:2015-10-02 09:57:54

标签: ios swift nskeyedunarchiver

如果原始类未知,

NSKeyedUnarchiver.decodeObject将导致崩溃/ SIGABRT。我所看到的解决此问题的唯一解决方案可以追溯到Swift的早期历史,并且需要使用Objective C(也是早期的Swift 2&guard的实现,throws,{ {1}}& try)。我可以找出Objective C的路线 - 但如果可能的话,我更愿意理解一个仅限Swift的解决方案。

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

unarchiver.decodeObject()

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

throws方法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
}
//...

4 个答案:

答案 0 :(得分: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
    }
}

然后:

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}

答案 1 :(得分:17)

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

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

在任何需要的地方。

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

答案 2 :(得分:1)

实际上,这是我们应该深入挖掘问题的原因。有可能,您创建一个名为xxx.archive的归档路径,然后从路径(xxx.archive)取消归档,现在一切正常。但如果更改目标名称,当您取消归档时,崩溃就会发生!这是因为归档和解析了不同的对象(事实是我们归档和解压缩target.obj,而不仅仅是obj)。     这么简单的方法是删除存档路径或只使用不同的存档路径。然后我们应该考虑如何避免崩溃,try-catch是rintaro提到的帮助器。

答案 3 :(得分:0)

我遇到了同样的问题。在类声明中添加@objc对我有用。

@objc(YourClass)
class YourClassName: NSObject {
}