无法解码 class 的对象

Cannot decode object of class


我正在尝试将 "Class" 发送到我的 Watchkit 扩展,但出现此错误。

* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyApp.Person)

存档和取消存档在 iOS 应用程序上工作正常,但在与 watchkit 扩展程序通信时却不行。怎么了?

InterfaceController.swift

    let userInfo = ["method":"getData"]

    WKInterfaceController.openParentApplication(userInfo,
        reply: { (userInfo:[NSObject : AnyObject]!, error: NSError!) -> Void in

            println(userInfo["data"]) // prints <62706c69 7374303...

            if let data = userInfo["data"] as? NSData {
                if let person = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Person {
                    println(person.name)
                }
            }

    })

AppDelegate.swift

func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
    reply: (([NSObject : AnyObject]!) -> Void)!) {

        var bob = Person()
        bob.name = "Bob"
        bob.age = 25

        reply(["data" : NSKeyedArchiver.archivedDataWithRootObject(bob)])
        return
}

Person.swift

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

注意: 虽然此答案中的信息是正确的,但 way 更好的答案是@agy 下面的答案。

这是由编译器从同一个 class 创建 MyApp.Person & MyAppWatchKitExtension.Person 引起的。这通常是由于在两个目标之间共享相同的 class 而不是 创建一个框架来共享它。

两个修复:

正确的解决方法是将 Person 提取到框架中。主应用程序和 watchkit 扩展都应该使用该框架,并且将使用相同的 *.Person class.

解决方法是在保存和传递之前将您的 class 序列化为 Foundation 对象(如 NSDictionary)。 NSDictionary 将在应用程序和扩展程序中进行编码和解码。一个好的方法是在 Person 上实施 RawRepresentable 协议。

我必须在设置框架后添加以下行才能使 NSKeyedUnarchiver 正常工作。

解档前:

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

存档前:

NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)

根据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.

通过添加注释 @objc(name),即使我们只使用 Swift,命名空间也会被忽略。让我们演示一下。假设 target A 定义了三个 类:

@objc(Adam)
class Adam:NSObject {
}

@objc class Bob:NSObject {
}

class Carol:NSObject {
}

如果目标 B 调用这些 类:

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

输出将是:

Adam
B.Bob
B.Carol

但是,如果目标 A 调用这些 类,结果将是:

Adam
A.Bob
A.Carol

要解决您的问题,只需添加 @objc(name) 指令:

@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")
    }
}

我有一个类似的情况,我的应用程序使用我的 Core 框架,我在其中保留了所有模型 类。例如。当我决定将所有 类 移动到 MyApp 时,我使用 NSKeyedArchiverNSKeyedUnarchiver 存储和检索了 UserProfile 对象 NSKeyedUnarchiver 开始抛出错误,因为存储的对象像 Core.UserProfile 而不是解压器预期的 MyApp.UserProfile。我如何解决它是创建 NSKeyedUnarchiver 的子类并覆盖 classforClassName 函数:

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

然后将 @objc(name) 添加到需要存档的 类,如此处的一个答案中所建议的那样。

并这样称呼它:

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

效果很好。

解决方案 NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName") 不适合我的原因是它不适用于嵌套对象,例如 UserProfile 有一个 var address: Address。 Unarchiver 在 UserProfile 时会成功,但在更深的 Address 时会失败。

@objc(name) 解决方案单独没有为我解决的原因是因为我没有从 OBJ-C 转移到 Swift,所以问题不是 UserProfile -> MyApp.UserProfile 而是 Core.UserProfile -> MyApp.UserProfile.

应用名称更改后我开始面临这个问题,

我得到的错误是 - “.....无法为密钥解码 class (MyOldModuleName.MyClassWhichISerialized) 的对象......”

这是因为代码默认保存的Archived对象带有ModuleName前缀,ModuleName改变后将无法定位。您可以从错误消息 class 前缀中识别旧模块名称,此处为“MyOldModuleName”。

我只是使用旧名称来定位旧的存档对象。 所以在 Unarchieving 添加行之前,

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

在归档之前添加行

NSKeyedArchiver.setClassName("MyOldModuleName.MyClassWhichISerialized", for: MyClassWhichISerialized.self)