后台播种/加载 iOS 核心数据存储

Posted

技术标签:

【中文标题】后台播种/加载 iOS 核心数据存储【英文标题】:Background Seeding/Loading iOS Core Data Store 【发布时间】:2016-01-02 13:39:00 【问题描述】:

我的 iOS 8.0 + 应用程序本质上是一个字典应用程序,以索引的、易于导航的格式向用户呈现只读数据集。我已经探索了几种加载静态数据的策略,并且我决定在应用程序首次打开时将几个JSON 数据文件序列化并加载到Core Data 存储中。因此,对managedObjectContext.save() 的调用将在应用的生命周期内仅在首次使用时发生一次。

通过阅读 Mac 开发人员库(2015 年 9 月更新)中 Apple 的 Core Data Programming Guide,我了解到Apple 的 建议做法是1) 将 Core Data 堆栈与 AppDelegate 分离成一个 dedicated DataController object (这使得即使在 Xcode 7.2 中仍然放置 Core Data 堆栈看起来很奇怪默认情况下在AppDelegate 中,但无论如何...);和 2) 使用dispatch_async 块在background thread 中打开(并且,我假设是种子/加载)持久存储,如下所示:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) 
    //(get URL to persistent store here)

    do 
        try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)

        //presumably load the store from serialized JSON files here?
     catch  fatalError("Error migrating store: \(error)") 

我刚刚开始学习并发和GCD,所以我的问题很基础:

1) 如果数据集正在后台线程中加载,这可能需要一些不小的时间才能完成,初始view controller 如何知道数据何时完成加载,以便它可以从ManagedObjectContext 显示在 UITableView 中?

2) 与此类似,如果我想通过运行一些提取并将调试文本打印到控制台来测试完全加载的数据集,我如何知道后台进程何时完成并且可以安全地开始查询?

谢谢!

p.s.我正在 swift 开发,所以任何关于 swift 的提示都会非常棒。

【问题讨论】:

@Hiren 请不要对仅将文字加粗的帖子进行琐碎的编辑,或对帖子本身的内容进行最小的更改。 【参考方案1】:

您可以import the data yourself,然后添加只读 .sqlite 文件和数据模型,而不是尝试让您的应用在首次启动时导入只读数据(强制用户在导入数据时等待)到您的应用目标,以复制到应用程序包。

对于导入,指定持久存储应使用回滚日志选项,因为不建议将预写日志记录用于只读存储:

let importStoreOptions: [NSObject: AnyObject] = [
    NSSQLitePragmasOption: ["journal_mode": "DELETE"],]

在实际应用中,还要指定捆绑的持久化存储应该使用只读选项:

let readOnlyStoreOptions: [NSObject: AnyObject] = [
    NSReadOnlyPersistentStoreOption: true,
    NSSQLitePragmasOption: ["journal_mode": "DELETE"],]

由于捆绑的持久存储是只读的,因此可以直接从应用包中访问它,甚至不需要从包中复制到用户目录。

【讨论】:

感谢您的回答。出于几个原因,我决定避免使用文件复制解决方案,而更喜欢构建首次使用的解决方案。例如,在应用程序的未来版本中,我可以让用户在字典中写笔记,这些笔记将持久保存在 Core Data 存储中。此外,在未来的版本中,我可能会添加一个选项来从互联网上提取更新的 JSON 文件,以“修补”数据存储。还有其他几个原因,但本质上我希望现在能更好地理解异步/后台加载过程。 @vikingfo04 您的未来意图并不否定在首次发布之前预先播种商店的可能性。您可以简单地捆绑一个可写存储,该存储可以在未来的应用程序版本中迁移。或者,您可以将笔记保存在单独的存储中,无论您是否决定通过 URL 更新词典存储。无论哪种方式,您仍然可以迁移和合并您的数据,但这样您就不必维护代码来最初导入数据。 @vikingfo04 但回到你的问题,这里有两篇著名的文章讨论了importing data 和using Core Data in the background 的方法。【参考方案2】:

暂且不说在第一次启动时加载 JSON 是否是最佳选择,而且这个问题已有四年之久,您的两个问题的解决方案可能是使用通知。它们在所有线程中工作,并且每个监听类实例都会收到通知。另外,您只需要添加两行:

    侦听器(您的视图控制器或问题 2 的测试类)需要侦听特定通知名称的通知:

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleMySeedNotification(_:)), name: "com.yourwebsite.MyCustomSeedNotificationName", object: nil)

@objc func handleMySeedNotification(_ notification: Notification) 是一个函数,您将在其中实现收到通知时应该发生的任何事情。

    调用者(您的数据库逻辑)发送数据导入成功的通知。这看起来像这样:

    NotificationCenter.default.post(name: "com.yourwebsite.MyCustomSeedNotificationName", object: nil)

这就够了。我个人喜欢使用Notification.Name 的扩展名,以便更快地访问名称并防止拼写错误。这是可选的,但工作方式如下:

extension Notification.Name 
    static let MyCustomName1 = Notification.Name("com.yourwebsite.MyCustomSeedNotificationName1")
    static let MyCustomName2 = Notification.Name("CustomNotificationName2")

现在使用它们变得像这样简单:NotificationCenter.default.post(name: .MyCustomSeedNotificationName1, object: nil),甚至在输入点后还可以完成代码!

【讨论】:

以上是关于后台播种/加载 iOS 核心数据存储的主要内容,如果未能解决你的问题,请参考以下文章

在后台保存会导致响应时间延迟(iOS)

Entity Framework 6 Code first:如何使用“循环”关系和存储生成的列来播种数据?

核心数据 - 在后台保存到磁盘上的持久存储

如何在 cellEdit 之后强制从持久存储重新加载核心数据

我可以从服务器检索数据并将其存储在核心数据中吗?

核心数据和 iOS 数据存储指南