Search code examples
iosswiftwatchkit

Cannot decode object of class



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

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

Archiving and unarchiving works fine on the iOS App but not while communicating with the watchkit extension. What's wrong?

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

Solution

  • 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.