NSManagedObjectContext应该在哪里创建? [英] Where should NSManagedObjectContext be created?

查看:200
本文介绍了NSManagedObjectContext应该在哪里创建?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近一直在学习Core Data,特别是如何做大量对象的插入。学习如何做到这一点,并解决我遇到的内存泄漏问题,我写了问题& A 在Swift中使用大型Core Data批处理插入的内存泄漏



更改 NSManagedObjectContext 从一个类属性到一个局部变量,并且也批量保存插入,而不是一次一个,它工作了很多更好。



我在我的回答中发布的代码是

  let batchSize = 1000 

//为每批数据插入
做一些循环while(thereAreStillMoreObjectsToAdd){

//获取管理对象上下文
let managedObjectContext =(UIApplication.sharedApplication()。delegate as!AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil //如果你不需要撤消任何东西

//获取要插入的下一个1000个左右的数据项
let array = nextBatch(batchSize)//你自己的实现

// insert everything in这个批处理
对于数组中的项{

//解析数组项,或者做任何你需要的操作来获取要插入的新对象的实体属性
// ...

//插入新对象
let newObject = NSEntityDescription.insertNewObjectForEntityForName(MyEntity,inManagedObjectContext:managedObjectContext)as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}

// save上下文
do {
try managedObjectContext.save()
} catch {
print(error)
}
}

这种方法似乎对我很好。我在这里问一个问题的原因是两个人(谁知道更多的iOS,而不是我做的)发表评论,我不明白。



@ Mundi说: p>


在您的代码中,您使用的是同一个托管对象上下文,
不是新的。


@ MartinR还说


...通常实现是一个惰性属性在应用程序的生命周期中创建一次
上下文。在这种情况下,你正在重用
与Mundi说的相同的上下文。


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



以前,我在View Controller中声明了上下文,在 viewDidLoad 中初始化它,到我的实用程序类做插入,并只是用它的一切。发现大内存泄漏后,我开始只是在本地创建上下文。



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


应该通常为导入创建一个单独的受管对象上下文
,并将其undo管理器设置为nil。 (上下文不是
特别昂贵的创建,所以如果你缓存你的持久
商店协调器,你可以使用不同的上下文为不同的工作
集或不同的操作。)


b h2_lin>解决方案


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


在代码的第一部分...

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

如果有人想把这个伪代码转换成swift,我很好。



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



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



其他的东西,比如抓取检查重复的,或者有复杂的关系,那么有可能MOC可能不是完全空的。



因此,你可能想添加在保存之后,swift等效于 [moc reset] ,以确保MOC确实为空。


I've recently been learning about Core Data and specifically how to do inserts with a large number of objects. After learning how to do this and solving a memory leak problem that I met, I wrote the Q&A Memory leak with large Core Data batch insert in Swift.

After changing NSManagedObjectContext from a class property to a local variable and also saving inserts in batches rather than one at a time, it worked a lot better. The memory problem cleared up and the speed improved.

The code I posted in my answer was

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

This method seems to be working well for me. The reason I am asking a question here, though, is two people (who know a lot more about iOS than I do) made comments that I don't understand.

@Mundi said:

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

@MartinR also said:

... 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.

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?

Previously, I had declared the context in my View Controller, initialized it in viewDidLoad, passed it as a parameter to my utility class doing the inserts, and just used it for everything. After discovering the big memory leak is when I started just creating the context locally.

One of the other reasons I started creating the contexts locally is because the documentation said:

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.)

What is the standard way to use 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?

Let's look at the first part of your code...

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

Now, since it appears you are keeping your MOC in the App Delegate, it's likely that you are using the template-generated Core Data access code. Even if you are not, it is highly unlikely that your managedObjectContext access method is returning a new MOC each time it is called.

Your managedObjectContext variable is merely a reference to the MOC that is living in the App Delegate. Thus, each time through the loop, you are merely making a copy of the reference. The object being referenced is the exact same object each time through the loop.

Thus, I think they are saying that you are not using separate contexts, and I think they are right. Instead, you are using a new reference to the same context each time through the loop.


Now, your next set of questions have to do with performance. Your other post references some good content. Go back and look at it again.

What they are saying is that if you want to do a big import, you should create a separate context, specifically for the import (Objective C since I have not yet made time to learn Swift).

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

You would then attach that MOC to the Persistent Store Coordinator. Using performBlock you would then, in a separate thread, import your objects.

The batching concept is correct. You should keep that. However, you should wrap each batch in an auto release pool. I know you can do it in swift... I'm just not sure if this is the exact syntax, but I think it's close...

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

In pseudo-code, it would all look something like this...

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

If someone wants to turn that pseudo-code into swift, it's fine by me.

The autorelease pool ensures that any objects autoreleased as a result of creating your new objects are released at the end of each batch. Once the objects are released, the MOC should have the only reference to objects in the MOC, and once the save happens, the MOC should be empty.

The trick is to make sure that all object created as part of the batch (including those representing the imported data and the managed objects themselves) are all created inside the autorelease pool.

If you do other stuff, like fetching to check for duplicates, or have complex relationships, then it is possible that the MOC may not be entirely empty.

Thus, you may want to add the swift equivalent of [moc reset] after the save to ensure that the MOC is indeed empty.

这篇关于NSManagedObjectContext应该在哪里创建?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆