应该在哪里创建 NSManagedObjectContext?

Where should NSManagedObjectContext be created?

我最近一直在学习 Core Data,特别是如何对大量对象进行插入。在学习了如何做到这一点并解决了我遇到的内存泄漏问题之后,我写了问答 .

NSManagedObjectContext 从 class 属性 更改为局部变量并分批保存插入而不是一次保存插入后,效果好多了。内存问题解决了,速度提升了。

我在回答中发布的代码是

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 的了解比我多)发表了我不理解的评论。

:

It seems in your code you are using the same managed object context, not a new one.

:

... the "usual" implementation is a lazy property which creates the context once for the lifetime of the app. In that case you are reusing the same context as Mundi said.

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

之前,我在我的视图控制器中声明了上下文,在 viewDidLoad 中对其进行了初始化,将其作为参数传递给我的实用程序 class 来执行插入操作,然后将其用于所有操作。在发现大内存泄漏之后,我开始在本地创建上下文。

我开始在本地创建上下文的另一个原因是因为 documentation 说:

First, you should typically create a separate managed object context for the import, and set its undo manager to nil. (Contexts are not particularly expensive to create, so if you cache your persistent store coordinator you can use different contexts for different working sets or distinct operations.)

标准的使用方法是什么NSManagedObjectContext

Now I don't understand. Are they saying I am using the same managed object context or I should use the same managed object context? If I am using the same one, how is it that I create a new one on each while loop? Or if I should be using just one global context, how do I do it without causing memory leaks?

让我们看看您的代码的第一部分...

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

现在,由于您似乎将 MOC 保留在 App Delegate 中,很可能您正在使用模板生成的核心数据访问代码。即使您不是,您的 managedObjectContext 访问方法每次调用时都不太可能返回新的 MOC。

您的 managedObjectContext 变量只是对存在于 App Delegate 中的 MOC 的引用。因此,每次通过循环,您只是在制作引用的副本。每次循环中引用的对象都是完全相同的对象。

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


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

他们的意思是,如果你想做一个大导入,你应该创建一个单独的上下文,专门用于导入(Objective 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()
    }
}

如果有人想把那个伪代码变成swift,我没问题。

自动释放池确保任何因创建新对象而自动释放的对象都在每批结束时释放。一旦对象被释放,MOC 应该只有对 MOC 中对象的引用,一旦保存发生,MOC 应该是空的。

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

如果你做其他事情,比如抓取以检查重复项,或者有复杂的关系,那么 MOC 可能不完全是空的。

因此,您可能希望在保存后添加相当于 [moc reset] 的 swift,以确保 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<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 栈如何工作的更多资源: