核心数据中删除大量(10.000+)对象的最有效方法是什么?

我试图删除多组10.000 + NSManagedObjects的方式太内存密集(大约20MB活字节),我的应用程序正在被抛弃。 这里是删除方法的实现:

+ (void)deleteRelatedEntitiesInManagedObjectContext:(NSManagedObjectContext *)context { NSFetchRequest *fetch = [[NSFetchRequest alloc] init]; [context setUndoManager:nil]; [fetch setEntity:[NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:context]]; [fetch setIncludesPropertyValues:NO]; NSError *error = nil; NSArray *entities = [context executeFetchRequest:fetch error:&error]; NSInteger deletedCount = 0; for (NSManagedObject *item in entities) { [context deleteObject:item]; deletedCount++; if (deletedCount == 500) { [context save:&error]; deletedCount = 0; } } if (deletedCount != 0) { [context save:&error]; } } 

我试过:-setFetchBatchSize,但是还有更多的内存被使用。

什么会是一个更有记忆效率的方式来做到这一点?

编辑 :刚刚看过2015年WWDC“ 核心数据有什么新东西 ”(它总是我看的第一个video,但我今年一直很忙),他们宣布了一个新的API: NSBatchDeleteRequest应该比任何以前的解决scheme。


有效率有多重意义,通常意味着某种权衡。 在这里,我假设你只是想在删除时包含内存。

核心数据有许多性能选项,超出了任何单个SO问题的范围。

如何pipe理内存取决于您的managedObjectContext和fetchRequest的设置。 看看文档,看看所有的选项。 不过,尤其要记住这些。

另外,请记住性能方面。 这种types的操作应该在一个单独的线程上执行。

此外,请注意,对象图的其余部分也将发挥作用(因为CoreData如何处理相关对象的删除。

关于记忆消耗,MOC特别注意两个特性。 虽然这里有很多,但它并不是全面的。 如果你想真正看到发生了什么事情,每次保存操作之前和之后都要logging你的MOC。 特别是,log registeredObjects和deletedObjects。

  1. MOC有一个注册对象的列表。 默认情况下,它不保留已注册的对象。 但是,如果retaininsRegisteredObjects为YES,它将保留所有注册的对象。

  2. 对于特别的删除,setPropagatesDeletesAtEndOfEvent告诉MOC如何处理相关的对象。 如果您希望用保存处理它们,则需要将该值设置为NO。 否则,将等待当前事件完成

  3. 如果你有非常大的对象集,可以考虑使用fetchLimit。 尽pipe缺点并不占用大量的记忆,但它们仍然需要一些,而且每次数千个并不是微不足道的。 这意味着更多的提取,但是你会限制内存的数量

  4. 另外考虑,任何时候你有大的内部循环,你应该使用自己的自动释放池。

  5. 如果这个MOC有一个父母,保存只把这些变化移到父母。 在这种情况下,如果你有一个母公司MOC,你只是让这个成长。

为了限制内存,考虑这个(对于你的情况不一定是最好的 – 有很多的核心数据选项 – 只有你知道什么是最适合你的情况,根据你在别处使用的所有选项。

我在NSManagedObjectContext上编写了一个类,用于保存,当我想确保保存到后台存储,非常类似于这个。 如果你不使用MOC层次结构,你不需要它,但是……没有理由不使用层次结构(除非你绑定到旧的iOS)。

 - (BOOL)cascadeSave:(NSError**)error { __block BOOL saveResult = YES; if ([self hasChanges]) { saveResult = [self save:error]; } if (saveResult && self.parentContext) { [self.parentContext performBlockAndWait:^{ saveResult = [self.parentContext cascadeSave:error]; }]; } return saveResult; } 

我修改了一下你的代码

 + (void)deleteRelatedEntitiesInManagedObjectContext:(NSManagedObjectContext *)context { NSFetchRequest *fetch = [[NSFetchRequest alloc] init]; [context setUndoManager:nil]; [fetch setEntity:[NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:context]]; [fetch setIncludesPropertyValues:NO]; [fetch setFetchLimit:500]; NSError *error = nil; NSArray *entities = [context executeFetchRequest:fetch error:&error]; while ([entities count] > 0) { @autoreleasepool { for (NSManagedObject *item in entities) { [context deleteObject:item]; } if (![context cascadeSave:&error]) { // Handle error appropriately } } entities = [context executeFetchRequest:fetch error:&error]; } } 

在一个灵感的时刻,我删除了[fetch setIncludesPropertyValues:NO]; 这很好。 从文档:

在正常提取期间(includesPropertyValues为YES),核心数据获取匹配logging的对象ID和属性数据,用信息填充行caching,并将被pipe理对象作为错误返回(请参阅returnsObjectsAsFaults)。 这些错误是pipe理对象,但是它们的所有属性数据仍然驻留在行caching中,直到错误被触发。 当错误被触发时,核心数据从行caching中检索数据 – 不需要返回到数据库。

我设法减less分配的活动字节大约13MB,这是更好的。

NSBatchDeleteRequest为我工作; 减less了删除pipe理对象的时间5倍,没有内存尖峰。

这将是一个有趣的testing,尝试使用魔法logging 。 这里有一个truncate方法,应该是非常有效的(我已经在3000条logging的数据集上使用了它,没有问题,看看它是如何处理10,000条的。

我不会仅仅为了这个function而使用它,如果你还没有尝试过,你应该这样做。 它使处理核心数据变得更容易,代码更less。

希望这可以帮助。

我绝不会对它进行testing,但是如果内存是你主要关心的问题,你可以尝试在额外的自动释放池中封装那些500个删除的批处理。 上下文可能是这样的:保存会创build相当多的自动释放对象,直到完成运行循环才能释放。 有了10 000多条logging,它可以很好地加起来。

如果您不想使用其他API,请尝试使用NSFetchRequest另一个functionfetchLimit ,或者与fetchOffset一起使用。 我在一个iTunes U iPad课程中看到过这个例子,这个例子涉及到大量的数据处理。

 NSInteger limit = 500; NSInteger count = [context countForFetchRequest:fetch error:nil]; [fetch setFetchLimit:limit]; for (int i=0; i < count/limit+1; i++) { // do the fetch and delete and save } 

您可以调整fetchLimit以满足您的内存要求。