用户默认值从 Obj C 迁移到 Swift

User Defaults migration from Obj C to Swift

我正在为我的 OS X 应用开发一个 Update,它最初是用 Obj-C 编写的。
更新已在 Swift

中重新编写

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

所有本机类型(如 Bool, String)用户首选项都工作正常,但 NSCoding 兼容的 Class 无法 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. 起初我以为是 class 名称不同(在 SwiftPerson 而不是 KSPerson)。但是更改 class 名称(改回 KSPerson)并没有解决问题
  2. 为了让 class 更像 Objective C 我也尝试在 class 前面加上 @objc

这是代码

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 copyObjective C 代码,并且它完美地反序列化(由原始副本序列化的内容)。
出乎我的意料。当我在 duplicate copy 中将 class KSPerson 重命名为 JSPerson 时,它给了我与上面

完全相同的错误

所以有一件事很清楚。您需要具有相同的 class 名称才能取消存档 NSCoding 兼容 objects。但这显然不足以 Swift

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

您需要告诉解档器如何将归档名称映射到模块的 class 名称。您可以使用 setClass(_:forClassName:):

集中执行此操作
NSKeyedUnarchiver.setClass(KSPerson.self, forClassName: "KSPerson")

这是一个全局注册,适用于所有不覆盖它的解压器。您当然可以为同一个 class 注册多个名称以用于取消存档。例如:

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)

这只会影响注册后存档的 classes。它不会修改现有档案。

这仍然很脆弱。 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] ?? []

这returns [] 如果有问题而不是崩溃。它仍然尊重全球 class 名称注册。