NSKeyedArchiver 并在目标之间共享自定义类

Posted

技术标签:

【中文标题】NSKeyedArchiver 并在目标之间共享自定义类【英文标题】:NSKeyedArchiver and sharing a custom class between targets 【发布时间】:2018-07-13 00:26:29 【问题描述】:

我的应用使用自定义类作为其数据模型:

class Drug: NSObject, NSCoding 
    // Properties, methods etc...

我刚刚创建了一个 Today 扩展,需要从中访问用户的数据,所以我使用 NSCoding 将我的数据保存在应用容器和共享容器中。这些是主应用程序中的保存和加载功能:

func saveDrugs() 
    // Save to app container
    let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(drugs, toFile: Drug.ArchiveURL.path)
    if isSuccessfulSave 
        print("Drugs successfully saved locally")
     else 
        print("Error saving drugs locally")
    

    // Save to shared container for extension
    let isSuccessfulSaveToSharedContainer = NSKeyedArchiver.archiveRootObject(drugs, toFile: Drug.SharedArchiveURL.path)
    if isSuccessfulSaveToSharedContainer 
        print("Drugs successfully saved to shared container")
     else 
        print("Error saving drugs to shared container")
    


func loadDrugs() -> [Drug]? 
    return NSKeyedUnarchiver.unarchiveObject(withFile: Drug.ArchiveURL.path) as? [Drug]

我遇到了类命名空间问题,我的 Today 扩展中的 NSKeyedUnarchiver 无法正确解码对象,所以我使用了this answer 并在类定义前添加了@objc

@objc(Drug)
class Drug: NSObject, NSCoding 
    // Properties, methods etc...

这完美地解决了这个问题。但是,这将是我的应用程序的 1.3 版,这似乎破坏了预先存在的数据的取消归档过程(我认为它可能会这样)。

处理这种情况最好的方法是什么,好像我只是做了这个改变,新版本的应用程序会为现有用户崩溃!

我找不到任何其他答案,我不确定NSKeyedArchiver.setClass() 方法是否相关,也不确定在哪里使用它。

我们将不胜感激地接受任何帮助。谢谢。

【问题讨论】:

应用为什么会崩溃? 嗨@matt 它崩溃了,因为一旦我添加了@objc(Drug) 行,NSKeyedUnarchiver 就无法解码旧的 Drug 类 (myApp.Drug) - 该类现在变为 *.Drug,它可以工作在应用程序和扩展程序之间共享时,但不解码旧的用户数据。新用户就好了。 【参考方案1】:

这正是NSKeyedUnarchiver.setClass(_:forClassName:) 的用例——您可以通过关联当前类的旧名称来向前迁移旧类:

let unarchiver = NSKeyedUnarchiver(...)
unarchiver.setClass(Drug.self, forClassName: "myApp.Drug")
// unarchive as appropriate

或者,您可以提供一个符合 NSKeyedUnarchiverDelegate 的委托并提供一个 unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:),但这对于这种情况可能有点过头了。


请记住,这适用于向前迁移旧数据。如果您有更新版本的应用程序需要将数据发送到旧版本的应用程序,您需要在存档方面做类似的事情 - 继续使用 NSKeyedArchiver.setClassName(_:for:) 使用旧名称对新类进行编码:

let archiver = NSKeyedArchiver(...)
archiver.setClassName("myApp.Drug", for: Drug.self)
// archive as appropriate

如果您遇到此向后兼容性问题,那么很遗憾,只要有旧版本应用的用户,您可能需要继续使用旧名称。

【讨论】:

谢谢你!我只需要取消归档,因为我的 Today 扩展只需要读取数据而不是写入数据。一旦用户升级了应用程序并加载了他们存储的数据,它将以新的方式重新存档。澄清一下,您的实现是否会取消归档新的 (@objc(Drug)) 类和旧的类?我还需要使用@objc 吗? 我现在不在我的 Mac 上,但我稍后会对此进行测试并将你的标记为正确答案。谢谢。 @Ampoule 您仍然需要为该类指定一个 @objc 名称以使其在两个目标之间保持稳定,因此请保留 @objc(Drug)。为现有的类名设置一个类是附加的——它添加了一个从 "myApp.Drug"Drug 的映射,并允许您将 "Drug" 取消归档。 我试过这个,但它不会编译,因为我试图使用 unarchiver 实例的 unArchiveObject 方法,但它是一个静态方法,所以需要在 NSKeyedUnarchiver 上调用。但是,我该如何使用 setClass?我可以先做 NSKeyedUnarchiver.setClass(...) 然后 NSKeyedUnarchiver.unarchiveObject(...) 吗? @AlejandroViquez 无论您打算在哪里实际继续存档或取消存档数据。因此,无论哪种例程处理将数据保存到磁盘或协调它。这将根据您在应用程序中的设置方式而有所不同。或者,NSKeyedArchiverNSKeyedUnarchiver 具有执行相同操作的类方法(例如,NSKeyedArchiver.setClass(_:forClassName:) 会影响应用程序中的所有存档,您可以从 AppDelegate 或类似方法调用

以上是关于NSKeyedArchiver 并在目标之间共享自定义类的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 NSKeyedArchiver 在 UserDefaults 中保存自定义对象

如何使用 NSKeyedArchiver 对自定义类进行编码和解码

CoreData 可转换:自定义转换器永远不会被调用 - 使用 NSKeyedArchiver

ios NSKeyedArchiver 释放 = 错误访问

在 NSKeyedArchiver 中查找不同的实例

如何在 AppDelegate 和 ViewController 之间共享属性,并在 App 终止前保存