6

我试图删除多组 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,但使用的内存更多。

什么是更节省内存的方法来做到这一点?

4

6 回答 6

12

编辑:刚刚观看了 2015 年 WWDC “ Core Data 中的新功能”(它一直是我观看的第一个视频,但今年我一直很忙),他们宣布了一个新的 API:NSBatchDeleteRequest这应该比以前的任何解决方案都更有效率。


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

Core Data 有很多性能选项,超出了任何单个 SO 问题的范围。

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

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

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

关于内存消耗,MOC 有两个属性需要特别注意。虽然这里有很多,但绝不是全面的。如果您想实际查看发生了什么,请在每次保存操作之前和之后 NSLog 您的 MOC。特别是记录已注册对象和已删除对象。

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

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

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

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

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

对于限制内存,请考虑这一点(不一定最适合您的情况 - 有很多Core Data 选项 - 只有您知道什么最适合您的情况,基于您在其他地方使用的所有选项。

我在 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];
    }
}
于 2012-05-14T12:13:42.803 回答
1

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

在正常获取期间(includesPropertyValues 为 YES),Core Data 获取匹配记录的对象 ID 和属性数据,用信息填充行缓存,并将托管对象作为故障返回(请参阅returnsObjectsAsFaults)。这些故障是托管对象,但在触发故障之前,它们的所有属性数据仍驻留在行缓存中。当故障被触发时,Core Data 从行缓存中检索数据——无需返回数据库。

我设法将分配的活动字节减少到〜13MB,这更好。

于 2012-05-15T06:47:33.527 回答
1

NSBatchDeleteRequest 为我工作;将删除托管对象的时间减少了 5 倍,并且没有内存峰值。

于 2016-07-02T05:53:37.513 回答
0

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

于 2012-05-14T11:25:09.313 回答
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以满足您的内存要求。

于 2012-05-14T11:34:32.290 回答
0

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

我不会仅将它用于该功能,如果您还没有尝试过,那么您应该这样做。它使处理 Core Data 变得更加容易,并且使用更少的代码。

希望这可以帮助。

于 2012-05-14T11:00:54.307 回答