Вопрос по ios – Каков наиболее эффективный способ удаления большого количества (более 10.000) объектов в Базовых данных?

6

Способ, которым я пытаюсь удалить несколько наборов из 10.000+ NSManagedObjects, требует слишком много памяти (около 20 МБ живых байтов), и мое приложение отбрасывается. Вот реализация метода удаления:

+ (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, но там использовалось еще больше памяти.

Что может быть более эффективным для памяти способом сделать это?

Оптимизация, которая сработала: 1. удалить строку -setIncludesPropertyValues; 2. изменить предел выборки с 500 до 2500 (правда!); 3. используйте @autoreleasepool Victor Bogdan

Ваш Ответ

6   ответов
12

EDIT: Только что посмотрел 2015 WWDC & quot;What's New in Core Data& Quot; (это всегда первое видео, которое я смотрю, но в этом году я был очень занят), и они объявили о новом API:NSBatchDeleteRequest это должно быть гораздо более эффективным, чем любое предыдущее решение.

Эффективное имеет несколько значений и чаще всего означает некоторый компромисс. Здесь я предполагаю, что вы просто хотите сохранить память при удалении.

Основные данные имеютlots вариантов производительности, выходящих за рамки любого вопроса SO.

Способ управления памятью зависит от настроек вашего managedObjectContext и fetchRequest. Посмотрите на документы, чтобы увидеть все варианты. В частности, вы должны помнить об этом.

Также имейте в виду аспект производительности. Этот тип операции должен выполняться в отдельном потоке.

Также обратите внимание, что остальная часть вашего графа объектов также вступит в игру (из-за того, как CoreData обрабатывает удаление связанных объектов.

Что касается потребления памяти, на MOC следует обратить особое внимание на два свойства. В то время как здесь много, это ни в коем случае не близко к всестороннему. Если вы действительно хотите увидеть, что происходит, NSLog ваш MOC непосредственно перед и после каждой операции сохранения. В частности, регистрируются зарегистрированные объекты и удаленные объекты.

The MOC has a list of registered objects. By default, it does not retain registered objects. However, if retainsRegisteredObjects is YES, it will retain all registered objects.

For deletes in particular, setPropagatesDeletesAtEndOfEvent tells the MOC how to handle related objects. If you want them handled with the save, you need to set that value to NO. Otherwise, it will wait until the current event is done

If you have really large object sets, consider using fetchLimit. While faults do not take a lot of memory, they still take some, and many thousands at a time are not insignificant. It means more fetching, but you will limit the amount of memory

Also consider, any time you have large internal loops, you should be using your own autorelease pool.

If this MOC has a parent, saving only moves those changes to the parent. In this case, if you have a parent MOC, you are just making that one grow.

Для ограничения памяти, подумайте об этом (не обязательно лучше для вашего случая - естьlots опций 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];
    }
}
Используя пул автоматического выпуска, я сократил использование памяти с 20 МБ до всего 8 МБ. Каскадное сохранение для меня немного излишне, но я запомню это позже. Я также удалил строку -setIncludePropertyValues и использовал предел выборки. Victor Bogdan
я имеюa question связанные с использованиемpropagatesDeletesAtEndOfEvent.
Отличный ответ, спасибо! Victor Bogdan
1

сократил время удаления управляемых объектов в 5 раз без скачка памяти.

1

[fetch setIncludesPropertyValues:NO]; и это было хорошо Из документов:

During a normal fetch (includesPropertyValues is YES), Core Data fetches the object ID and property data for the matching records, fills the row cache with the information, and returns managed object as faults (see returnsObjectsAsFaults). These faults are managed objects, but all of their property data still resides in the row cache until the fault is fired. When the fault is fired, Core Data retrieves the data from the row cache—there is no need to go back to the database.

Мне удалось уменьшить выделенные живые байты до ~ 13 МБ, что лучше.

0

Волшебная запись, Там есть усеченный метод, который должен быть очень эффективным (я использовал его на наборах данных до 3000 записей без проблем. Интересно посмотреть, как он обрабатывает 10 000).

Я бы не использовал его только для этой функции, если вы его не попробовали, вы должны это сделать. Это делает работу с Core Data намного проще и с меньшим количеством кода.

Надеюсь это поможет.

Я делаю ниже примерно 16 тыс. Записей в CoreData, и приложение не работает. Проблемы с памятью. [MagicalRecord saveWithBlock: ^ (NSManagedObjectContext * localContext) {[Item MR_truncateAllInContext: localContext]; } завершение: ^ (BOOL success, NSError * error) {if (! IsEmpty (block)) {block (error); }}];
Спасибо, я попробую когда-нибудь. Victor Bogdan
0

NSFetchRequest, fetchLimitможет быть в сочетании с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 чтобы удовлетворить ваши требования к памяти.

Вы случайно не помните, какой это был курс? Я заинтересован в том, чтобы увидеть это ... Во всяком случае, у меня есть и ваше решение. Victor Bogdan
0

но если ваша основная проблема - память, вы можете попробовать инкапсулировать эти партии из 500 удалений в дополнительный пул авто-выпуска. Возможно, что context: save создает довольно много автоматически выпущенных объектов, которые не освобождаются до тех пор, пока вы не закончите с циклом цикла выполнения. С более чем 10 000 записей это может сложиться довольно хорошо.

Спасибо! Я тоже попробую. Victor Bogdan

Похожие вопросы