用户默认值从 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
我进行了以下尝试,但仍然无法找出解决方案
- 起初我以为是 class 名称不同(在
Swift
是 Person
而不是 KSPerson
)。但是更改 class 名称(改回 KSPerson
)并没有解决问题
- 为了让 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 copy
的 Objective 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 名称注册。
我正在为我的 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
我进行了以下尝试,但仍然无法找出解决方案
- 起初我以为是 class 名称不同(在
Swift
是Person
而不是KSPerson
)。但是更改 class 名称(改回KSPerson
)并没有解决问题 - 为了让 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 copy
的 Objective 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 名称注册。