无法解码类的对象

时间:2015-04-06 14:08:37

标签: ios swift watchkit


我正在尝试发送一个" Class"到我的Watchkit扩展,但是我收到了这个错误。

  

*由于未捕获的异常终止应用' NSInvalidUnarchiveOperationException',原因:' *    - [NSKeyedUnarchiver decodeObjectForKey:]:无法解码类的对象(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")
    }
}

5 个答案:

答案 0 :(得分:51)

根据Interacting with Objective-C APIs

  

在Swift类上使用@objc( 名称 )属性时,该类在Objective-C中可用,没有任何命名空间。因此,将可归档的Objective-C类迁移到Swift时,此属性也很有用。由于归档对象在归档中存储其类的名称,因此应使用@objc( 名称 )属性指定与Objective-C类相同的名称,以便更旧档案可以通过新的Swift课程取消归档。

通过添加注释@objc(name),即使我们只使用Swift,也会忽略命名空间。让我们来证明一下。想象一下,目标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(名称)指令:

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

答案 1 :(得分:28)

我必须在设置框架后添加以下行,以使NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName") 正常工作。

在取消归档之前:

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

归档之前:

wrap_content

答案 2 :(得分:15)

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

这是由编译器创建MyApp.Person&amp; MyAppWatchKitExtension.Person来自同一个班级。它通常是由两个目标共享同一个类而非创建一个框架来共享它。

两个修正:

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

解决方法是在保存&amp;之前将类序列化为Foundation对象(如NSDictionary)。交上来。 NSDictionary将是代码&amp;可在应用程序和扩展程序中解码。执行此操作的一种好方法是在RawRepresentable上实施Person协议。

答案 3 :(得分:9)

我有类似的情况,我的应用程序使用我的Core框架,我保留了所有模型类。例如。我使用UserProfileNSKeyedArchiver存储并检索NSKeyedUnarchiver对象,当我决定将所有类移到MyApp NSKeyedUnarchiver时,因为存储的对象是像unarchiver所期望的那样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")之所以不适合我,是因为它不适用于嵌套对象,例如UserProfilevar address: Address时。 Unarchiver将使用UserProfile成功,但当它更深入Address时会失败。

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

答案 4 :(得分:0)

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

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

这是因为默认情况下,代码会保存带有ModuleName前缀的Archived对象,在ModuleName更改后将无法找到该对象。您可以从错误消息类前缀中识别旧的模块名称,这里是“ MyOldModuleName”。

我只是使用旧名称来查找旧的Archived对象。 因此在取消存档添加行之前,

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

在存档前添加行

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