【问题标题】:Error creating a separate NSManagedObjectContext创建单独的 NSManagedObjectContext 时出错
【发布时间】:2015-05-06 21:38:38
【问题描述】:

在进入我的问题之前,请先看看这张图片。

这是实际的数据模型:

我从 Web API 检索一组记录,从中创建对象,将它们保存在核心数据中并在“今天”视图中显示它们。默认情况下,返回当前日期的这些记录。

用户可以点击过去按钮转到单独的视图,在该视图中他可以从日期选择器视图中选择过去或未来的日期,并查看该选定日期的记录。这意味着我必须再次调用 API 传递选定的日期,检索数据并将该数据保存在核心数据中并显示它们。当用户离开这个视图时,这些数据应该被丢弃。

这是重要的部分。即使我获得了一组新数据,“今天”视图中当前日期的旧原始数据也不能消失。因此,如果/当用户返回 Today 视图时,该数据应该在他离开时随时可用,而无需应用调用 API 并再次获取当前日期的数据。

我想创建一个单独的NSManagedObjectContext 来保存这些临时数据。

我有一个名为DatabaseManager 的单独类来处理与核心数据相关的任务。此类使用 `NSManagedObjectContext 的实例进行初始化。它在给定的上下文中创建托管对象类。

import CoreData
import Foundation
import MagicalRecord
import SwiftyJSON

public class DatabaseManager {

    private let context: NSManagedObjectContext!

    init(context: NSManagedObjectContext) {
        self.context = context
    }

    public func insertRecords(data: AnyObject, success: () -> Void, failure: (error: NSError?) -> Void) {
        let json = JSON(data)
        if let records = json.array {
            for recordObj in records {
                let record = Record.MR_createInContext(context) as Record
                record.id = recordObj["Id"].int
                record.name = recordObj["Name"].string!
                record.date = NSDate(string: recordObj["Date"].string!)
            }
            context.MR_saveToPersistentStoreAndWait()
            success()
        }
    }
}

所以在 Today 视图中,我将 NSManagedObjectContext.MR_defaultContext() 传递给 insertRecords() 方法。我还有一种方法可以从给定的上下文中获取记录。

func fetchRecords(context: NSManagedObjectContext) -> [Record]? {
    return Record.MR_findAllSortedBy("name", ascending: true, inContext: context) as? [Record]
}

从 API 中检索数据,保存在核心数据中并成功显示。到目前为止一切顺利。

在过去的观点中,我必须做基本相同的事情。但是因为我不想改变原始数据。我尝试了 MagicalRecord 提供的几种方法。

尝试 #1 - NSManagedObjectContext.MR_context()

我用NSManagedObjectContext.MR_context() 创建了一个新的上下文。我在过去视图中更改了日期,该选定日期的数据被成功检索并保存在数据库中。但这是问题所在。当我从核心数据中获取对象时,我也得到了旧数据。例如,每天只有 10 条记录。在今天视图中,我显示 10 条记录。当在过去视图中获取对象时,我得到 20 个对象!我假设它是旧的 10 个对象加上新的对象。此外,当我尝试在 tableview 中显示它们时,它会因cellForRowAtIndexPath 方法中的 EXC_BAD_ACCESS 错误而崩溃。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

    let record = records[indexPath.row]
    cell.textLabel?.text = record.name // EXC_BAD_ACCESS
    cell.detailTextLabel?.text = record.date.toString()

    return cell
}

尝试 #2 - NSManagedObjectContext.MR_newMainQueueContext()

当我更改日期并出现以下错误时,应用程序崩溃。

'+entityForName: nil 不是合法的 NSPersistentStoreCoordinator 用于搜索实体名称'Record''

尝试 #3 - NSManagedObjectContext.MR_contextWithParent(NSManagedObjectContext.MR_defaultContext())

与尝试 #1 的结果相同。

尝试 #4 - 从 Hal 的 Answer 中我了解到,即使我创建了两个 MOC,它们都引用了相同的 NSPersistentStore。所以我创建了另一个新商店来保存我的 AppDelegate 中的临时数据。

MagicalRecord.setupCoreDataStackWithStoreNamed("Records")
MagicalRecord.setupCoreDataStackWithStoreNamed("Records-Temp")

然后当我更改日期以获取新数据时,我将那个临时存储设置为默认存储。

func getDate(date: NSDate) {
    let url = NSPersistentStore.MR_urlForStoreName("Records-Temp")
    let store = NSPersistentStore(persistentStoreCoordinator: NSPersistentStoreCoordinator.MR_defaultStoreCoordinator(), configurationName: nil, URL: url, options: nil)
    NSPersistentStore.MR_setDefaultPersistentStore(store)

    let context = NSManagedObjectContext.MR_defaultContext()
    viewModel.populateDatabase(date, context: context)
}

请注意,我使用的是默认上下文。我得到了数据,但它与尝试 1 和 3 的结果相同。我得到 20 条记录。它们包括旧日期和新日期的数据。如果我使用NSManagedObjectContext.MR_context(),它只会像尝试 1 一样崩溃。

我还发现了其他东西。在 App Delegate 中创建商店后,我在今日视图中打印出默认的商店名称println(MagicalRecord.defaultStoreName())。奇怪的是,它没有打印出我给商店起的名字 Records。相反,它显示了 Reports.sqlite。报告是项目的名称。很奇怪。

为什么我也会得到旧数据?初始化新上下文时我在做什么?

对不起,如果我的问题有点令人困惑,所以我将一个演示项目上传到我的 Dropbox。希望这会有所帮助。

感谢任何帮助。

谢谢。

【问题讨论】:

  • 1.使用获取的结果控制器来管理表格视图中的结果。 2. 在构造谓词时使用日期字段作为您的 sortDescriptor - 这样您就不会得到意外的结果。我会花点时间写一篇文章来回答以上所有问题,我现在很忙,但一定要调查一下。另外,神奇的记录有一个 saveWithBlock 功能,你应该使用它来保存
  • 很遗憾我不能使用NSFetchedResultsController。我努力了。我在上面发布的示例是我正在从事的实际项目的一个大大简化的版本。在该应用程序中,数据以复杂的方式进行操作,无法在获取的结果控制器中复制。另外我不使用 saveWithBlock 的原因是数据获取需要根据客户的要求同步。我知道这很糟糕。谢谢你。在此期间我会继续努力。
  • 您几乎肯定希望所有内容都在同一个持久存储中。您正在为过去/未来视图创建并显示哪些实体的实例。
  • @HalMueller 在我的实际应用中,原始列表显示了当前日期的Handover 对象列表。用户可以根据需要更改日期并显示不同日期的移交。

标签: ios swift core-data nsmanagedobjectcontext magicalrecord


【解决方案1】:

在您的持久存储中并使用原始 MOC 寻址的旧数据仍然存在,并且将在第二个 MOC 进行提取时检索。他们都在看同一个持久存储。只是第二个 MOC 也有从你的 API 获取的新数据。

保存到 Core Data 的同步网络操作会挂起您的应用程序,并且(对于足够大的记录集)会导致系统终止您的应用程序,在用户看来是崩溃。您的客户在这一点上是错误的,需要接受教育。

分解获取、保存和查看的逻辑。您显示特定日期记录的视图应该这样做 - 如果它接受日期并使用谓词,它可以这样做。

您的“cellForRowAtIndexPath”崩溃听起来像是标识符丢失或拼写错误的问题。如果你硬编码一个字符串而不是使用'record.name'会发生什么?

【讨论】:

  • 感谢您的回复。如果我创建一个单独的NSPersistentStore 来保存新数据,那么在从中获取数据时,我可以使用相同的上下文还是也需要为此创建一个新的上下文?
  • 如果持久化存储附加到同一个 NSPersistentStoreCoordinator,你可以使用同一个上下文。但如果你问这个问题,我觉得你真的在与这个框架的设计模式作斗争。我们可以看一下您的实际数据模型的屏幕截图吗?
  • 当我创建一个新的NSPersistentStore 时,我是否还需要将它添加到一个新的NSPersistentStoreCoordinator 中?
  • 因为即使我有两个上下文和两个商店和一个协调员,我也会得到相同的重复结果。
  • 如果您添加了两次记录,您将看到它们两次。为这个问题使用多个持久存储是 IMO 错误的路径(除非您使用 assignObject:toStore 明确地将对象分配给存储,否则无关紧要)。一个正确编写的谓词,带有工人上下文,遵循丹尼尔加拉斯科的答案为你列出的模式,是要走的路。
【解决方案2】:

线程安全

首先我想提一下核心数据的黄金法则。 NSManagedObject 不是线程安全的,因此,“你不能跨越流”(WWDC)。这意味着你应该始终在其上下文中访问托管对象,并且永远不要将其传递到其上下文之外。这就是为什么你的导入器类让我担心,您将一堆对象插入到上下文中而不能保证您在上下文中运行插入。

一个简单的代码更改就可以解决这个问题:

public func insertRecords(data: AnyObject, success: () -> Void, failure: (error: NSError?) -> Void) {
    let json = JSON(data)
    context.performBlock { () -> Void in
        //now we are thread safe :)
        if let records = json.array {
            for recordObj in records {
                let record = Record.MR_createInContext(context) as Record
                record.id = recordObj["Id"].int
                record.name = recordObj["Name"].string!
                record.date = NSDate(string: recordObj["Date"].string!)
            }
            context.MR_saveToPersistentStoreAndWait()
            success()
        }
    }
}

唯一不需要担心的情​​况是当您使用主队列上下文并访问主线程上的对象时,例如在 tableview 等中。

不要忘记 MagicalRecord 也有方便的保存实用程序,可以创建适合保存的上下文:

MagicalRecord.saveWithBlock { (context) -> Void in
  //save me baby
}

显示旧记录

现在关于您的问题,您帖子中的以下段落与我有关:

用户可以点击过去按钮转到一个单独的视图,在那里他可以 从日期选择器视图中选择过去或未来的日期并查看记录 对于该选定的日期。这意味着我必须再次调用 API 传递选定的日期,检索数据并将该数据保存在 核心数据并显示它们。当用户离开这个视图时,这个数据 应该被丢弃。

我不喜欢您在用户离开该视图后丢弃用户请求的信息的想法。作为用户,我希望能够导航回旧列表并查看我刚刚查询的结果,而无需其他不必要的网络请求。使用删除实用程序在启动时而不是在用户访问它们时修剪旧对象可能更有意义。

无论如何,我无法说明您熟悉NSFetchedResultsController 的重要性

这个类旨在有效地管理从返回的结果 一个核心数据获取请求。

您使用获取请求配置此类的实例 指定实体,可选的过滤谓词和数组 包含至少一种排序顺序。当您执行 fetch 时, 实例有效地收集有关结果的信息,而无需 需要同时将所有结果对象放入内存。 当您访问结果时,对象会自动归入 批量内存以匹配可能的访问模式,以及来自的对象 以前访问的处置。这种行为进一步有助于保持 内存要求低,所以即使你遍历一个集合 包含数以万计的对象,你不应该有更多 超过几十个同时在内存中。

取自Apple

它确实为您完成了所有工作,并且应该是任何显示 Core Data 对象的列表的首选。

当我从核心数据中获取对象时,我也会得到旧数据

这是意料之中的,您没有指定您的提取应包含特定日期范围内的报告的任何地方。这是一个获取示例:

let fetch = Record.MR_createFetchRequest()
let maxDateForThisController = NSDate()//get your date
fetch.predicate = NSPredicate(format: "date < %@", argumentArray: [maxDateForThisController])
fetch.fetchBatchSize = 10// or an arbitrary number
let dateSortDescriptor = NSSortDescriptor(key: "date", ascending: false)
let nameSortDescriptor = NSSortDescriptor(key: "name", ascending: true)

fetch.sortDescriptors = [dateSortDescriptor,nameSortDescriptor]//the order in which they are placed in the array matters

let controller = NSFetchedResultsController(fetchRequest: fetch,
        managedObjectContext: NSManagedObjectContext.MR_defaultContext(),
        sectionNameKeyPath: nil, cacheName: nil)

导入可丢弃记录

最后,您说您想查看旧报告并使用不会保存到持久存储的单独上下文。这也很简单,你的导入器需要一个上下文,所以你需要做的就是确保你的导入器可以支持导入而不保存到持久存储。这样,您可以丢弃上下文,并且对象将随之而来。因此,您的方法签名可能如下所示:

public func insertRecords(data: AnyObject, canSaveToPersistentStore: Bool = true,success: () -> Void, failure: (error: NSError?) -> Void) {

    /**
      Import some stuff
    */
    if canSaveToPersistentStore {
        context.MR_saveToPersistentStoreWithCompletion({ (complete, error) -> Void in
            if complete {
                success()
            } else {
                error
            }
        })
    } else {
        success()
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-04
    • 2012-08-29
    • 1970-01-01
    相关资源
    最近更新 更多