Моему приложению для iPhone необходимо перенести основное хранилище данных, а некоторые базы данных довольно большие. В документации Apple предлагается использовать «несколько проходов» для переноса данных, чтобы уменьшить использование памяти. Однако документация очень ограничена и не очень хорошо объясняет, как на самом деле это сделать. Может ли кто-нибудь указать мне на хороший пример или подробно объяснить процесс, как на самом деле это осуществить?
85
Ответы:
Я разобрался, на что 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
комплекте.Необходимо указать целевое хранилище, которое не должно быть исходным хранилищем. После успешной миграции вы можете удалить старый и переименовать новый.
Я внес некоторые изменения в модель данных после создания моделей сопоставления, это привело к некоторым ошибкам совместимости, которые я мог решить только путем воссоздания моделей сопоставления.
источник
Эти вопросы связаны:
Проблемы с памятью при переносе больших хранилищ данных CoreData на iPhone
Многопроходная миграция основных данных по частям с iOS
Процитируем первую ссылку:
источник
Предположим, в вашей схеме базы данных есть 5 сущностей, например, человек, студент, курс, класс и регистрация, чтобы использовать стандартный пример, где ученик подкласса человек, класс реализует курс, а регистрация присоединяется к классу и ученику. Если вы внесли изменения во все эти определения таблиц, вам нужно начать с базовых классов и продвигаться вверх. Таким образом, вы не можете начать с преобразования регистраций, потому что каждая регистрационная запись зависит от наличия в ней класса и студентов. Итак, вы должны начать с миграции только таблицы Person, копирования существующих строк в новую таблицу и заполнения всех имеющихся там новых полей (если возможно) и удаления удаленных столбцов. Выполняйте каждую миграцию внутри пула автозапуска, чтобы после ее завершения ваша память вернулась к работе.
Как только таблица Person будет готова, вы можете преобразовать таблицу ученика. Затем перейдите к Курсу, затем к Классу и, наконец, к таблице регистрации.
Другое соображение - это количество записей. Если, например, у Person была тысяча строк, вам нужно было бы каждые 100 или около того выполнять NSManagedObject, эквивалент выпуска, который должен сообщить контексту управляемого объекта [moc refreshObject: ob mergeChanges: НЕТ]; Также установите низкий уровень таймера устаревших данных, чтобы память часто очищалась.
источник