我最近一直在学习核心数据,特别是如何使用大量对象进行插入。在学习了如何做到这一点并解决了我遇到的内存泄漏问题之后,我写了Q& A Memory leak with large Core Data batch insert in Swift。
将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)发表的评论让我不明白。
您的代码中似乎使用的是相同的托管对象上下文, 不是新的。
..."通常"实现是一个懒惰的属性,创建了 应用程序的生命周期一次上下文。在这种情况下,您正在重复使用 与蒙迪所说的相同。
现在我不明白。他们是说我是使用相同的托管对象上下文,还是我应该使用相同的托管对象上下文?如果我我使用相同的那个,那么我如何在每个while
循环上创建一个新的?或者如果我应该只使用一个全局上下文,我该如何做而不会导致内存泄漏?
以前,我已经在我的View Controller中声明了上下文,在viewDidLoad
中初始化它,将其作为参数传递给我的实用程序类进行插入,并将其用于所有内容。在发现大内存泄漏之后,我开始在本地创建上下文。
我开始在本地创建上下文的其他原因之一是因为documentation说:
首先,您通常应该创建一个单独的托管对象上下文 用于导入,并将其撤消管理器设置为nil。 (上下文不是 特别昂贵的创建,所以如果你缓存你的持久性 商店协调员您可以使用不同的上下文进行不同的工作 设置或不同的操作。)
使用NSManagedObjectContext
的标准方式是什么?
答案 0 :(得分:3)
现在我不明白。他们是说我使用相同的托管 对象上下文还是我应该使用相同的托管对象上下文?如果我 我使用的是同一个,我是怎么在每次创建一个新的 环?或者,如果我只使用一个全局上下文,我该怎么做 没有造成内存泄漏?
让我们看一下代码的第一部分......
while (thereAreStillMoreObjectsToAdd) {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil
现在,由于看起来您将MOC保留在App Delegate中,因此您可能正在使用模板生成的Core Data访问代码。即使您不是,每次调用时,managedObjectContext
访问方法都不太可能返回新的MOC。
您的managedObjectContext
变量仅仅是对位于App Delegate中的MOC的引用。因此,每次循环,您只是复制参考。每次循环时,被引用的对象都是完全相同的对象。
因此,我认为他们说你没有使用单独的背景,我认为他们是正确的。相反,每次循环时都使用对相同上下文的新引用。
现在,您的下一组问题与性能有关。您的其他帖子引用了一些好的内容。回去看看它。
他们所说的是,如果你想做一个大的导入,你应该创建一个单独的上下文,专门用于导入(目标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确实为空。
答案 1 :(得分:1)
这是@ 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<MyManagedObject>? = 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堆栈如何工作的更多资源: