Swift only way to prevent NSKeyedUnarchiver.decodeObject crash?

后端 未结 4 544
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-13 18:10

NSKeyedUnarchiver.decodeObject will cause a crash / SIGABRT if the original class is unknown. The only solution I have seen to catching this issue

相关标签:
4条回答
  • 2020-12-13 18:43

    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.

    0 讨论(0)
  • 2020-12-13 18:49

    I was having same issue. Adding @objc to class declaration worked for me.

    @objc(YourClass)
    class YourClassName: NSObject {
    }
    
    0 讨论(0)
  • 2020-12-13 18:54

    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.

    0 讨论(0)
  • 2020-12-13 19:06

    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 = delegate
    
    unarchiver.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}
    
    0 讨论(0)
提交回复
热议问题