Cannot decode object of class

后端 未结 5 1660
难免孤独
难免孤独 2020-12-08 04:54


I am trying to send a \"Class\" to my Watchkit extension but I get this error.

* Terminating app due to uncaught exception \'NSInvalidUna

相关标签:
5条回答
  • 2020-12-08 05:09

    I had a similar situation where my app used my Core framework in which I kept all model classes. E.g. I stored and retrieved UserProfile object using NSKeyedArchiver and NSKeyedUnarchiver, when I decided to move all my classes to MyApp NSKeyedUnarchiver started throwing errors because the stored objects were like Core.UserProfile and not MyApp.UserProfile as expected by the unarchiver. How I solved it was to create a subclass of NSKeyedUnarchiver and override classforClassName function:

    class SKKeyedUnarchiver: NSKeyedUnarchiver {
        override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
            let lagacyModuleString = "Core."
            if let range = codedName.range(of: lagacyModuleString), range.lowerBound.encodedOffset == 0  {
                return NSClassFromString(codedName.replacingOccurrences(of: lagacyModuleString, with: ""))
            }
            return NSClassFromString(codedName)
        }
    }
    

    Then added @objc(name) to classes which needed to be archived, as suggested in one of the answers here.

    And call it like this:

    if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
        currentUserProfile = unarchivedObject
    }
    

    It worked very well.

    The reason why the solution NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName") was not for me because it doesn't work for nested objects such as when UserProfile has a var address: Address. Unarchiver will succeed with the UserProfile but will fail when it goes a level deeper to Address.

    And the reason why the @objc(name) solution alone didn't do it for me was because I didn't move from OBJ-C to Swift, so the issue was not UserProfile -> MyApp.UserProfile but instead Core.UserProfile -> MyApp.UserProfile.

    0 讨论(0)
  • 2020-12-08 05:19

    I had to add the following lines after setting up the framework to make the NSKeyedUnarchiver work properly.

    Before unarchiving:

    NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
    

    Before archiving:

    NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
    
    0 讨论(0)
  • 2020-12-08 05:22

    I started facing this after the App Name change,

    The error I got was - ".....cannot decode object of class (MyOldModuleName.MyClassWhichISerialized) for key....."

    This is because code by default saves Archived object with ModuleName prefix, which will not be locatable after ModuleName changes. You can identify the old Module Name from the error message class prefix, which here is  "MyOldModuleName". 

    I simply used the old names to locate the old Archived objects. So before Unarchieving add line,

    NSKeyedUnarchiver.setClass(MyClassWhichISerialized.self, forClassName: "MyOldModuleName.MyClassWhichISerialized")
    

    And before Archieving add line

    NSKeyedArchiver.setClassName("MyOldModuleName.MyClassWhichISerialized", for: MyClassWhichISerialized.self)
    
    0 讨论(0)
  • 2020-12-08 05:23

    NOTE: While the information in this answer is correct, the way better answer is the one below by @agy.

    This is caused by the compiler creating MyApp.Person & MyAppWatchKitExtension.Person from the same class. It's usually caused by sharing the same class across two targets instead of creating a framework to share it.

    Two fixes:

    The proper fix is to extract Person into a framework. Both the main app & watchkit extension should use the framework and will be using the same *.Person class.

    The workaround is to serialize your class into a Foundation object (like NSDictionary) before you save & pass it. The NSDictionary will be code & decodable across both the app and extension. A good way to do this is to implement the RawRepresentable protocol on Person instead.

    0 讨论(0)
  • 2020-12-08 05:27

    According to Interacting with Objective-C APIs:

    When you use the @objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when you migrate an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the @objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.

    By adding the annotation @objc(name), namespacing is ignored even if we are just working with Swift. Let's demonstrate. Imagine target A defines three classes:

    @objc(Adam)
    class Adam:NSObject {
    }
    
    @objc class Bob:NSObject {
    }
    
    class Carol:NSObject {
    }
    

    If target B calls these classes:

    print("\(Adam().classForCoder)")
    print("\(Bob().classForCoder)")
    print("\(Carol().classForCoder)")
    

    The output will be:

    Adam
    B.Bob
    B.Carol
    

    However if target A calls these classes the result will be:

    Adam
    A.Bob
    A.Carol
    

    To resolve your issue, just add the @objc(name) directive:

    @objc(Person)
    class Person : NSObject, NSCoding {
        var name: String!
        var age: Int!
    
        // MARK: NSCoding
    
        required convenience init(coder decoder: NSCoder) {
            self.init()
            self.name = decoder.decodeObjectForKey("name") as! String?
            self.age = decoder.decodeIntegerForKey("age")
        }
    
        func encodeWithCoder(coder: NSCoder) {
            coder.encodeObject(self.name, forKey: "name")
            coder.encodeInt(Int32(self.age), forKey: "age")
        }
    }
    
    0 讨论(0)
提交回复
热议问题