Swift只能防止NSKeyedUnarchiver.decodeObject崩溃?

如果原始类未知, NSKeyedUnarchiver.decodeObject将导致崩溃/ SIGABRT 。 我所看到的解决这个问题的唯一解决scheme来自Swift的早期历史,并且需要使用Objective C(也是Swift 2之前的guardthrowstrycatch )。 我可以弄清Objective-C的路线 – 但是如果可能的话,我宁愿理解一个Swift-only解决scheme。

例如 – 数据已经使用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的唯一方法来testing数据是否有效之前尝试.decodeObject – 因为.decodeObject没有throws – 这意味着trycatch似乎不是一个在Swift中的选项(方法没有throws不能被包装据我所知)。 否则,解码数据的另一种方法将抛出一个错误,我可以赶上解码失败。 我希望用户能够从iCloud驱动器或Dropbox导入文件,因此需要进行适当的validation。 我不能认为编码的数据是安全的。

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

或者是否有人知道解决这个问题的唯一方法Swift 2? 我相信我感兴趣的关键可能是$classname – 但是TBH在我试图研究如何实现validateValue方面还有很深的含义 – 甚至是否是正确的路线来坚持下去。 我有一种感觉,我失去了一些明显的东西。


编辑:这是一个解决scheme – 感谢rintaro下面的很好的答案

最初的答案解决了我的问题 – 即实施一个代表。

但是现在我已经用rintaro的附加编辑响应构build了一个解决scheme,如下所示:

 //... 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遇到未知类时, NSKeyedUnarchiver unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:)委托方法。

例如,委托可以加载一些代码来将类引入到运行时并返回类,或者replace不同的类对象 。 如果委托返回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. 

已添加

我没有注意到, NSCoder有更快速的方法extension

 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} 

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

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

在需要的地方。

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