在Core Data中删除大量(10.000+)对象的最有效方法是什么?

时间:2012-05-14 10:27:14

标签: ios core-data

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

+ (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,但是使用的内存更多。

什么是一种更节省内存的方法呢?

6 个答案:

答案 0 :(得分:12)

编辑:刚刚观看了2015年WWDC“核心数据中的新功能”(它始终是我观看的第一个视频,但今年我一直非常忙碌)他们宣布了一个新API:NSBatchDeleteRequest,它应该比以前的解决方案更有效率。


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

核心数据具有批次的性能选项,超出任何单个SO问题的范围。

如何管理内存取决于managedObjectContext和fetchRequest的设置。查看文档以查看所有选项。但是,你应该记住这些事情。

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

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

关于内存消耗,MOC上有两个属性需要特别注意。虽然这里有很多,但它绝不是全面的。如果您想真正了解发生的情况,请在每次保存操作之前和之后对您的MOC进行NSLog。特别是,log registeredObjects和deletedObjects。

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

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

  3. 如果您有非常大的对象集,请考虑使用fetchLimit。虽然故障不占用大量内存,但它们仍然占用一些,而且一次数千个并不是无足轻重的。这意味着更多的提取,但你将限制内存量

  4. 还要考虑,每当你有大型内部循环时,你应该使用自己的自动释放池。

  5. 如果此MOC具有父级,则save仅将这些更改移动到父级。在这种情况下,如果你有一个父母MOC,你只是让那个成长。

  6. 对于限制内存,请考虑这一点(不一定最适合您的情况 - 有批次核心数据选项 - 只有您根据所有选项知道哪种情况最适合您的情况正在其他地方使用。

    我在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];
        }
    }
    

答案 1 :(得分:1)

在灵感的瞬间,我删除了[fetch setIncludesPropertyValues:NO];,这很好。来自文档:

  

在正常提取期间(includesPropertyValues为YES),核心数据   获取匹配记录的对象ID和属性数据,   使用信息填充行缓存,并返回托管对象   作为错误(请参阅returnsObjectsAsFaults)。管理这些故障   对象,但它们的所有属性数据仍驻留在行缓存中   直到故障发生。当故障被触发时,Core Data会检索   来自行缓存的数据 - 没有必要回到   数据库中。

我设法将分配的实时字节减少到~13MB,这样更好。

答案 2 :(得分:1)

NSBatchDeleteRequest为我工作;将管理对象的删除时间缩短了5倍,没有内存峰值。

答案 3 :(得分:0)

这将是一个有趣的测试,尝试使用Magical Record。在那里有一个截断方法应该是非常有效的(我已经在数据集上使用了大到3000条记录而没有问题。有趣的是看它如何处理10,000。

我不会仅仅将它用于该功能,如果你还没有尝试过,你应该这样做。它使得处理核心数据变得更加容易,而且代码更少。

希望这会有所帮助。

答案 4 :(得分:0)

我没有测试它,但如果内存是您主要关注的问题,您可以尝试在额外的自动释放池中封装这些500个删除批次。上下文:save可能会创建相当多的自动释放对象,这些对象在完成运行循环周期之前不会被释放。凭借10 000多条记录,它可以很好地加起来。

答案 5 :(得分:0)

如果您不想使用其他API,请尝试使用NSFetchRequestfetchLimit的其他功能,也可以与fetchOffset结合使用。我在其中一个iTunes U iPad课程中看到了这个例子,其中涉及使用Core Data进行大量数据处理。

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以满足您的内存要求。