Swift only way to prevent NSKeyedUnarchiver.decodeObject crash? Swift only way to prevent NSKeyedUnarchiver.decodeObject crash? ios ios

Swift only way to prevent NSKeyedUnarchiver.decodeObject crash?


When NSKeyedUnarchiver encounters unknown classes, unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:) delegate method is called.

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.

So, you can implement the delegate like this:

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    }}

Then:

let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)let delegate = MyUnArchiverDelegate()unarchiver.delegate = delegateunarchiver.decodeObjectForKey("root")// -> `nil` if the root object is unknown class.

ADDED:

I didn't noticed that NSCoder has extension with more swifty methods:

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?}

You can:

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}


another way is to fix the name of the class used for NSCoding. You simply have to use:

  • NSKeyedArchiver.setClassName("List", forClass: List.self before serializing
  • NSKeyedUnarchiver.setClass(List.self, forClassName: "List") before deserializing

wherever needed.

Looks like iOS extensions prefix the class name with the extension's name.


Actually, it's the reason which we should dig deeply matters. There's a possible, you create a archive path named xxx.archive, then you unarchive from the path(xxx.archive), now everything is ok. But if change target name, when you unarchive, the crash occurred!!! It's because archive&unarchive the different object(the truth is we archive&unarchive target.obj, not just the obj). so simple way is to delete the archive path or just use a different archive path. And then we should consider how avoid the crash, try-catch is our helper mentioned by rintaro.