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

85

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

Джейсон
источник
у вас действительно были проблемы с памятью? Является ли ваша миграция легковесной или вы хотите использовать NSMigrationManager?
Ник Уивер,
Да, консоль GDB показала, что были предупреждения о памяти, а затем приложение вылетало из-за ограниченного объема памяти. Я пробовал как облегченную миграцию, так и NSMigrationManager, но сейчас я пытаюсь использовать NSMigrationManager.
Джейсон
хорошо, не могли бы вы подробнее рассказать, что изменилось?
Ник Уивер,
наконец, я узнал, прочтите мой ответ.
Ник Уивер,
Привет, Джейсон, не могли бы вы исправить то же самое в вопросе?
Yuchen Zhong

Ответы:

174

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

Версия модели данных 1

введите описание изображения здесь введите описание изображения здесь

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

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

введите описание изображения здесь

Версия модели данных 2

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

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

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

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

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

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

1. Новый файл -> Ресурс -> Модель сопоставления введите описание изображения здесь

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

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

введите описание изображения здесь

Отображение модели, шаг первый

введите описание изображения здесь

введите описание изображения здесь

введите описание изображения здесь

Многопроходная миграция не требует политик миграции настраиваемых сущностей, однако мы сделаем это, чтобы получить более подробную информацию для этого примера. Итак, мы добавляем к сущности настраиваемую политику. Это всегда подкласс NSEntityMigrationPolicy.

введите описание изображения здесь

Этот класс политики реализует некоторые методы для выполнения нашей миграции. Однако это просто в этом случае , поэтому мы должны реализовать только один метод: 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:потому что метод класса вернет только одну, возможно первую, найденную модель сопоставления в пакете.

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

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комплекте.

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

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

Ник Уивер
источник
59
Черт возьми, это сложно. О чем думала Apple?
арот
7
Я не знаю, но всякий раз, когда я думаю, что основные данные - это хорошая идея, я изо всех сил стараюсь найти более простое и удобное в обслуживании решение.
Ник Уивер
5
Благодарность! Это превосходный ответ. Это кажется сложным, но не так уж и плохо, если вы выучите шаги. Самая большая проблема в том, что в документации это не так подробно описано.
Бентфорд
2
Вот обновленная ссылка на настройку процесса миграции. Он был перемещен с момента написания этого сообщения. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430
@NickWeaver, как вы определяете destinationStoreURL? Вы создаете его, или он создается основной системой данных в процессе миграции ????
dev gr
3

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

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

Многопроходная миграция основных данных по частям с iOS

Процитируем первую ссылку:

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

оккулус
источник
1
Спасибо за ссылки. Проблема в том, что на самом деле никто не объясняет подробно, как настроить его в несколько проходов. Как мне настроить несколько моделей сопоставления, чтобы они работали эффективно?
Джейсон
-5

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

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

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

ПапаСмурф
источник
Итак, вы по существу предлагаете новую схему основных данных, которая не является частью старой схемы, и копировать данные в новую схему вручную?
Джейсон
-1 Вручную отображать вашу базу данных не нужно. Вы можете перенести развернутые базы данных с помощью упрощенной миграции или явных моделей MappingModels.
Бентфорд