无法解码 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
时,我使用 NSKeyedArchiver
和 NSKeyedUnarchiver
存储和检索了 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)
我正在尝试将 "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
时,我使用 NSKeyedArchiver
和 NSKeyedUnarchiver
存储和检索了 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)