用户默认从Obj C迁移到Swift

时间:2015-12-22 09:36:46

标签: objective-c swift macos cocoa nsuserdefaults

我正在为Update应用OS X工作,该应用最初是用Obj-C编写的。
此更新已在Swift

中重写

我遇到一个奇怪的用户默认值处理问题。(因为更新时不得更改用户首选项)

所有原生类型(如Bool, String)用户首选项都正常运行,但符合NSCoding的类无法deserialse / Unarchive。这是一个错误:

Error :[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (KSPerson) for key (NS.objects); the class may be defined in source code or a library that is not linked

我做了以下尝试,但仍然无法找出解决方案

  1. 最初我认为这是由于不同的班级名称(在 Swift Person而不是KSPerson)。但是更改班级名称(回到KSPerson)并没有解决问题
  2. 为了让课程更像Objective C,我也尝试用@objc
  3. 来加班

    这是代码

    let UD = NSUserDefaults.standardUserDefaults()
    
    func serialize() {
        // Info: personList: [KSPerson]
        let encodedObject: NSData = NSKeyedArchiver.archivedDataWithRootObject(personList)
        UD.setObject(encodedObject, forKey: "key1")
        print("Serialization complete")
    }
    
    func deserialise() {
        let encodedObject: NSData = UD.objectForKey("key1") as! NSData
        let personList: [KSUrlObject] = NSKeyedUnarchiver.unarchiveObjectWithData(encodedObject) as! [KSPerson]
    
        for person in personList {
            print(person)
        }
    

    注意:
    我创建了一个duplicate copy Objective C代码,并且完全反序列化了(原始副本序列化了)。 出乎我的意料。当我在KSPerson中将班级JSPerson重命名为duplicate copy时,它给了我与上面相同的错误

    所以有一件事是清楚的。您需要具有与Unarchive NSCoding兼容objects相同的类名。但对Swift

    来说,这显然是不够的

1 个答案:

答案 0 :(得分:12)

Swift类包括它们的模块名称。 Objective-C类没有。因此,当您取消归档Objective-C类" KSPerson"时,Swift将查看其所有类并查找" mygreatapp.KSPerson"并得出结论,他们不匹配。

您需要告诉unarchiver如何将存档名称映射到模块的类名。您可以使用setClass(_:forClassName:)集中进行此操作:

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

这是一项全球注册,适用于所有不会覆盖它的unarchivers。您当然可以为同一个类注册多个名称以进行取消归档。例如:

NSKeyedUnarchiver.setClass(KSPerson.self, forClassName: "KSPerson")
NSKeyedUnarchiver.setClass(KSPerson.self, forClassName: "mygreatapp.Person")
NSKeyedUnarchiver.setClass(KSPerson.self, forClassName: "TheOldPersonClassName")

编写Swift存档时,默认情况下它将包含模块名称。这意味着如果您更改了模块名称(或在一个模块中存档并在另一个模块中取消存档),您将无法取消存档数据。这有一些好处(它可以防止意外取消归档为错误的类型),但在许多情况下你可能不希望这样。如果没有,您可以通过在另一个方向注册映射来覆盖它:

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

这仅影响注册后归档的类。它不会修改现有的档案。

这仍然非常脆弱。 unarchiveObjectWithData:在遇到问题时会引发ObjC异常,而Swift无法捕获。您将在糟糕的数据上崩溃。您可以使用-decodeObjectForKey而不是+unarchiveObjectWithData来避免这种情况:

let encodedObject: NSData = UD.objectForKey("key1") as? NSData ?? NSData()
let unarchiver = NSKeyedUnarchiver(forReadingWithData: encodedObject)
let personList : [KSPerson] = unarchiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as? [KSPerson] ?? []

如果出现问题而不是崩溃,则会返回[]。它仍然尊重全球类名注册。