我最近一直在学习有关核心数据的知识,尤其是关于如何对大量对象进行插入的知识。在学习了如何做到这一点并解决了我遇到的内存泄漏问题之后,我在Swift中编写了带有大型Core Data批处理插入的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所说的相同上下文。
现在我不明白。他们是说我正在使用相同的托管对象上下文还是应该使用相同的托管对象上下文?如果我正在使用相同的一个,它是如何创建的每个新的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
变量仅是对驻留在应用程序委托中的MOC的引用。因此,每次循环时,您只是在复制参考。每次循环,被引用的对象都是完全相同的对象。
因此,我认为他们说的是您没有使用单独的上下文,并且我认为他们是正确的。而是每次循环时都使用对相同上下文的新引用。
现在,您的下一组问题与性能有关。您的其他帖子引用了一些很好的内容。回去再看一遍。
他们的意思是,如果您想进行大量导入,则应创建一个单独的上下文,专门用于导入(由于我还没有时间去学习Swift,所以使用目标C)。
NSManagedObjectContext moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
然后,您可以将该MOC附加到持久性存储协调器。performBlock
然后,使用您将在单独的线程中导入对象。
批处理概念是正确的。你应该保持那个。但是,您应将每个批次包装在自动释放池中。我知道您可以迅速做到这一点...我只是不确定这是否是确切的语法,但我认为它已经接近了...
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确实为空。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句