Пример или объяснение миграции основных данных с несколькими проходами?

Мое приложение iPhone должно перенести его хранилище основных данных, а некоторые из них довольно большие. Документация Apple предлагает использовать "несколько проходов" для переноса данных для уменьшения использования памяти. Однако документация очень ограничена и не очень хорошо объясняет, как на самом деле это сделать. Может ли кто-нибудь указать мне на хороший пример или подробно объяснить процесс фактического снятия этого?

Ответ 1

Я понял, что Apple намекает в их документации. Это на самом деле очень легко, но долгий путь, прежде чем он станет очевидным. Я проиллюстрирую объяснение на примере. Исходная ситуация такова:

Модель данных, версия 1

enter image description hereenter image description here

Это модель, которую вы получаете, когда создаете проект с помощью шаблона "приложение на основе навигации с основным хранилищем данных". Я скомпилировал его и сделал несколько сложных ударов с помощью цикла for, чтобы создать около 2 тыс. Записей с разными значениями. Там мы идем 2000 событий со значением NSDate.

Теперь мы добавим вторую версию модели данных, которая выглядит следующим образом:

enter image description here

Модель данных, версия 2

Разница в том, что сущность Event исчезла, и у нас есть два новых. Тот, который хранит метку времени как double а второй, который должен хранить дату как NSString.

Цель состоит в том, чтобы перенести все события версии 1 в два новых объекта и преобразовать значения в процессе миграции. Это приводит к удвоению значений каждого из них как отдельного типа в отдельном объекте.

Для миграции мы выбираем миграцию вручную, и это мы делаем с моделями сопоставления. Это также первая часть ответа на ваш вопрос. Мы сделаем миграцию в два этапа, потому что для переноса записей 2k потребуется много времени, и мы хотим, чтобы объем памяти оставался низким.

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

Вернемся к нашим двум картографическим моделям.

Мы создаем первую модель отображения следующим образом:

1. Новый файл → Ресурс → Модель отображения enter image description here

2. Выберите имя, я выбрал StepOne

3. Установите исходную и целевую модель данных.

enter image description here

Модель отображения Шаг первый

enter image description here

enter image description here

enter image description here

Многопроходная миграция не нуждается в пользовательских политиках миграции объектов, однако мы сделаем это, чтобы получить немного больше подробностей для этого примера. Таким образом, мы добавляем пользовательскую политику к объекту. Это всегда подкласс NSEntityMigrationPolicy.

enter image description here

Этот класс политики реализует несколько методов, позволяющих осуществить нашу миграцию. Однако в этом случае все просто, поэтому нам придется реализовать только один метод: createDestinationInstancesForSourceInstance:entityMapping:manager:error:

Реализация будет выглядеть так:

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

Последний шаг: сама миграция

Я пропущу часть для настройки второй модели отображения, которая практически идентична, просто timeIntervalSince1970, используемый для преобразования NSDate в double.

Наконец, нам нужно запустить миграцию. Я пока пропущу шаблонный код. Если вам это нужно, я выложу здесь. Его можно найти в разделе "Настройка процесса миграции". Это просто слияние первых двух примеров кода. Третья и последняя часть будут изменены следующим образом: вместо использования метода класса NSMappingModel класса mappingModelFromBundles:forSourceModel:destinationModel: мы будем использовать initWithContentsOfURL: потому что метод класса вернет только одну, возможно, первую найденную модель отображения в расслоение

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

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

Заметки

  • Модель отображения заканчивается на cdm в расслоении.

  • Хранилище назначения должно быть предоставлено и не должно быть хранилищем источника. После успешной миграции вы можете удалить старую и переименовать новую.

  • Я внес некоторые изменения в модель данных после создания моделей отображения, что привело к некоторым ошибкам совместимости, которые я мог исправить только путем воссоздания моделей отображения.

Ответ 2

Эти вопросы связаны:

Проблемы с памятью, переносящие большие хранилища данных CoreData на iPhone

Миграция данных с несколькими перевалами в кусках с iOS

Чтобы процитировать первую ссылку:

Это обсуждается в официальном документация в разделе "Несколько проходов" но, похоже, их предлагаемый подход состоит в том, чтобы разделить ваша миграция по типу объекта, т.е. сделать несколько моделей сопоставления, каждый из которые переносят подмножество объекта типы из полной модели данных.

Ответ 3

Предположим, что ваша схема базы данных имеет 5 сущностей, например. человек, ученик, курс, класс и регистрация, чтобы использовать стандартный вид примера, когда ученик-подкласс, класс реализует курс, а регистрация присоединяется к классу и ученику. Если вы внесли изменения во все эти определения таблиц, вам нужно начинать с базовых классов и прокладывать себе путь вверх. Таким образом, вы не можете начинать с конвертирования регистраций, потому что каждая запись регистрации зависит от наличия у нее класса и учеников. Итак, вы начнете с переноса только таблицы Person, копирования существующих строк в новую таблицу и заполнения любых новых полей (если возможно) и отбрасывания удаленных столбцов. Выполняйте каждую миграцию внутри пула автозапуска, так что, как только это будет сделано, ваша память вернется к началу.

Как только таблица Person будет выполнена, вы можете преобразовать таблицу ученика. Затем переходите к курсу, а затем к классу, и, наконец, к таблице регистрации.

Другим соображением является количество записей, если у Лица было тысяча строк, вам приходилось бы каждые 100 или около того выполнить эквивалент выпуска NSManagedObject, который должен указывать контекст управляемого объекта [moc refreshObject: ob mergeChanges: NO]; Также установите слишком низкий уровень таймера данных, так что память часто очищается.