Swift only way to prevent NSKeyedUnarchiver.decodeObject crash?

后端 未结 4 550
佛祖请我去吃肉
佛祖请我去吃肉 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条回答
  •  猫巷女王i
    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(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(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}
    

提交回复
热议问题