Swift只能防止NSKeyedUnarchiver.decodeObject崩溃?
如果原始类未知, NSKeyedUnarchiver.decodeObject
将导致崩溃/ SIGABRT
。 我所看到的解决这个问题的唯一解决scheme来自Swift的早期历史,并且需要使用Objective C(也是Swift 2之前的guard
, throws
, try
和catch
)。 我可以弄清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
– 这意味着try
– catch
似乎不是一个在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扩展在类名的后面加上扩展名。