Cannot decode object of class
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.
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")
}
}
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)