创build单独的NSManagedObjectContext时出错

在进入我的问题之前,请看看这个图像。

在这里输入图像说明

这里是实际的数据模型: 数据模型

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

用户可以点击“过去”button转到单独的视图,他可以从dateselect器视图中select过去或将来的date,并查看该选定date的logging。 这意味着我必须再次调用API来传递选定的date,检索数据并将这些数据保存在核心数据中并显示出来。 当用户离开这个视图时,这个数据应该被丢弃。

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

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

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

 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()方法。 我也有一个方法来从给定的上下文中获取logging。

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

数据从API中检索,保存在核心数据中,并成功显示。 迄今为止都很好。

在过去的观点中,我必须做基本相同的事情。 但是因为我不希望原始数据改变。 我试图用MagicalRecord提供的几种方法来做到这一点。

尝试#1NSManagedObjectContext.MR_context()

我用NSManagedObjectContext.MR_context()创build一个新的上下文。 我在“过去”视图中更改date,所选date的数据将被检索并成功保存到数据库中。 但这是问题。 当我从核心数据中获取对象时,我也会得到这些旧数据。 例如,每天只有10条logging。 在今日视图中,我显示10条logging。 在Past视图中获取对象时,我得到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 } 

尝试#2NSManagedObjectContext.MR_newMainQueueContext()

当我更改date以下错误时,应用程序崩溃。

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

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

与尝试#1相同的结果。

尝试#4 – 从哈尔的答案我了解到,即使我创build两个MOC,他们都指的是相同的NSPersistentStore 。 所以我创build了另一个新的商店来暂存我的AppDelegate中的临时数据。

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

然后,当我更改date以获取新数据时,我将该临时存储设置为这样的默认存储。

 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条logging。 它们包括旧date和新date的数据。 如果我使用NSManagedObjectContext.MR_context() ,它只会像尝试1一样崩溃。

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

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

对不起,如果我的问题是有点混乱,所以我上传了一个演示项目到我的Dropbox 。 希望这会有所帮助。

任何帮助表示赞赏。

谢谢。

线程安全

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

一个简单的代码更改可以解决此问题

 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() } } } 

唯一不需要担心的是当你使用Main Queue Context和访问主线程上的对象,比如在tableview等等

不要忘记,MagicalRecord也有方便的保存工具,可以创build上下文保存的时机:

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

显示旧logging

现在到您的问题,您的文章中的以下段落关注我:

用户可以点击“过去”button转到单独的视图,他可以从dateselect器视图中select过去或将来的date,并查看该选定date的logging。 这意味着我必须再次调用API来传递选定的date,检索数据并将这些数据保存在核心数据中并显示出来。 当用户离开这个视图时,这个数据应该被丢弃。

我不喜欢这样的想法,即一旦离开该视图,就丢弃用户请求的信息。 作为一个用户,我希望能够回到旧列表,看到我刚刚查询的结果,而没有其他不必要的networking请求。 也许更有意义的是,可能有一个删除实用程序,在启动时修剪旧对象,而不是在用户访问它们时。

无论如何,我不能说明你熟悉NSFetchedResultsController是多么重要

此类旨在有效地pipe理从核心数据获取请求返回的结果。

您可以使用指定实体的提取请求来configuration此类的实例,可选地使用filter谓词,以及包含至less一个sorting顺序的数组。 执行提取时,实例可以高效地收集有关结果的信息,而无需将所有结果对象同时存入内存。 在访问结果时,对象会自动分批存储到内存中,以匹配可能的访问模式,并处理先前访问的对象。 这个行为进一步保证了内存需求的低下,所以即使你遍历一个包含数以万计的对象的集合,你也不应该在内存中同时拥有数十个以上的对象。

采取从苹果计算机

它从字面上为你做所有的事情,应该是你的任何列表,显示核心数据的对象。

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

这是可以预料的,你没有指定任何地方,你的提取应该包括在某个date范围内的报告。 这是一个示例提取:

 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) 

导入可丢弃的logging

最后,你说你想看到旧的报告,并使用一个单独的上下文,不会保存到持久存储。 这也很简单,你的import商需要一个上下文,所以你需要做的是确保你的import商可以支持import而不储存到持久性商店。 这样,你可以放弃上下文,对象将随之而去。 所以你的方法签名可能看起来像这样:

 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() } } 

原始MOC中存储的旧数据仍然存在,当第二个MOC进行读取时将被检索。 他们都在看同一个持久性商店。 只是第二个MOC也有你的API提取的新数据。

同步networking操作保存到核心数据将挂起您的应用程序,并(对于足够大的logging集)导致系统杀死您的应用程序,出现在用户崩溃。 你的客户在这一点上是错误的,需要接受教育。

分解你的逻辑来获取,保存和查看。 显示特定datelogging的视图应该这样做 – 如果它接受date并使用谓词,则可以这样做。

您的“cellForRowAtIndexPath”崩溃的气味就像一个缺less或拼写错误的标识符的问题。 如果你硬编码一个string,而不是使用'record.name',会发生什么?