NSManagedObjectContext应该在哪里创建?

我最近一直在学习Core Data,特别是如何使用大量对象进行插入。 在学习了如何做到这一点并解决了我遇到的内存泄漏问题后,我在Swift中编写了大量核心数据批量插入的Q&A 内存泄漏 。

NSManagedObjectContext从类属性更改为局部变量并同时保存批量插入而不是一次保存插入后,它的工作效果要好得多。 内存问题得到解决,速度提高了。

我在答案中发布的代码是

 let batchSize = 1000 // do some sort of loop for each batch of data to insert while (thereAreStillMoreObjectsToAdd) { // get the Managed Object Context let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext managedObjectContext.undoManager = nil // if you don't need to undo anything // get the next 1000 or so data items that you want to insert let array = nextBatch(batchSize) // your own implementation // insert everything in this batch for item in array { // parse the array item or do whatever you need to get the entity attributes for the new object you are going to insert // ... // insert the new object let newObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject newObject.attribute1 = item.whatever newObject.attribute2 = item.whoever newObject.attribute3 = item.whenever } // save the context do { try managedObjectContext.save() } catch { print(error) } } 

这种方法对我来说似乎很有效。 我在这里问一个问题的原因是两个人(他们比我更了解iOS)发表了我不理解的评论。

@Mundi说 :

您的代码中似乎使用的是相同的托管对象上下文,而不是新的上下文。

@MartinR还说 :

…“通常”实现是一个惰性属性,它为应用程序的生命周期创建一次上下文。 在这种情况下,你正在重复使用与Mundi相同的背景。

现在我不明白。 他们是说我使用相同的托管对象上下文,还是应该使用相同的托管对象上下文? 如果我使用相同的一个,那么我如何在每个while循环中创建一个新的? 或者,如果我只使用一个全局上下文,我该如何做而不会导致内存泄漏?

以前,我在View Controller中声明了上下文,在viewDidLoad初始化它,将它作为参数传递给我的实用程序类进行插入,并将其用于所有内容。 在发现大内存泄漏之后,我开始在本地创建上下文。

我开始在本地创建上下文的其他原因之一是因为文档说:

首先,您通常应为导入创建单独的托管对象上下文,并将其撤消管理器设置为nil。 (上下文创建起来并不是特别昂贵,因此如果您缓存持久性存储协调器,则可以针对不同的工作集或不同的操作使用不同的上下文。)

使用NSManagedObjectContext的标准方法是什么?

现在我不明白。 他们是说我使用相同的托管对象上下文,还是应该使用相同的托管对象上下文? 如果我使用相同的一个,那么我是如何在每个while循环中创建一个新的? 或者,如果我只使用一个全局上下文,我该如何做而不会导致内存泄漏?

让我们看看代码的第一部分……

 while (thereAreStillMoreObjectsToAdd) { let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext managedObjectContext.undoManager = nil 

现在,由于看起来您将MOC保留在App Delegate中,因此您可能正在使用模板生成的Core Data访问代码。 即使不是这样,每次调用时,您的managedObjectContext访问方法都不太可能返回新的MOC。

您的managedObjectContext变量仅仅是对生活在App Delegate中的MOC的引用。 因此,每次循环,您只是复制参考。 被引用的对象是每次循环时完全相同的对象。

因此,我认为他们说你没有使用单独的上下文,我认为他们是正确的。 相反,您每次通过循环时都使用对相同上下文的新引用。


现在,您的下一组问题与性能有关。 您的其他post引用了一些好的内容。 回去再看一遍。

他们所说的是,如果你想做一个大的导入,你应该创建一个单独的上下文,专门用于导入(目标C,因为我还没有时间学习Swift)。

 NSManagedObjectContext moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 

然后,您将该MOC附加到持久性存储协调器。 然后使用performBlock ,在单独的线程中导入对象。

批处理的概念是正确的。 你应该保留它。 但是,您应该将每个批处理包装在自动释放池中。 我知道你可以在swift中做到这一点……我只是不确定这是不是确切的语法,但我认为它很接近……

 autoreleasepool { for item in array { let newObject = NSEntityDescription.insertNewObjectForEntityForName ... newObject.attribute1 = item.whatever newObject.attribute2 = item.whoever newObject.attribute3 = item.whenever } } 

在伪代码中,它看起来都像这样……

 moc = createNewMOCWithPrivateQueueConcurrencyAndAttachDirectlyToPSC() moc.performBlock { while(true) { autoreleasepool { objects = getNextBatchOfObjects() if (!objects) { break } foreach (obj : objects) { insertObjectIntoMoc(obj, moc) } } moc.save() moc.reset() } } 

如果有人想把这个伪代码变成快速的话,那对我来说没问题。

自动释放池确保在每个批处理结束时释放由于创建新对象而自动释放的任何对象。 释放对象后,MOC应该只对MOC中的对象进行引用,一旦保存发生,MOC应为空。

诀窍是确保作为批处理的一部分创建的所有对象(包括表示导入数据和托管对象本身的对象)都在自动释放池中创建。

如果您执行其他操作,例如获取以检查重复项,或具有复杂关系,那么MOC可能不会完全为空。

因此,您可能希望在保存后添加快速等效的[moc reset]以确保MOC确实为空。

这是@JodyHagins回答的补充答案。 我提供了那里提供的伪代码的Swift实现。

 let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType) managedObjectContext.persistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator // or wherever your coordinator is managedObjectContext.performBlock { // runs asynchronously while(true) { // loop through each batch of inserts autoreleasepool { let array: Array? = getNextBatchOfObjects() if array == nil { break } for item in array! { let newEntityObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject newObject.attribute1 = item.whatever newObject.attribute2 = item.whoever newObject.attribute3 = item.whenever } } // only save once per batch insert do { try managedObjectContext.save() } catch { print(error) } managedObjectContext.reset() } } 

这些是更多资源,帮助我进一步了解Core Data堆栈的工作原理:

  • Swift中的核心数据栈 – 揭秘
  • 我的核心数据堆栈