У меня есть проект iOS с большой предварительно загруженной базой данных и небольшой пользовательской базой данных (оба хранилища CoreData SQLite). Предыдущие вопросы предлагали использовать конфигурации для управления тем, какие объекты используются с каким магазином. У меня проблемы с тем, чтобы заставить это работать. Вот что я пытался...
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) return _managedObjectModel;
// set up the model for the preloaded data
NSURL *itemURL = [[NSBundle mainBundle] URLForResource:@"FlagDB" withExtension:@"momd"];
NSManagedObjectModel *itemModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:itemURL];
// set up the model for the user data
NSURL *userDataURL = [[NSBundle mainBundle] URLForResource:@"UserData" withExtension:@"momd"];
NSManagedObjectModel *userDataModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:userDataURL];
// merge the models
_managedObjectModel = [NSManagedObjectModel modelByMergingModels:[NSArray arrayWithObjects:itemModel, userDataModel, nil]];
// define configurations based on what was in each model
WRONG [_managedObjectModel setEntities:itemModel.entities forConfiguration:@"ItemData"];
WRONG [_managedObjectModel setEntities:userDataModel.entities forConfiguration:@"UserData"];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator;
// preloaded data is inside the bundle
NSURL *itemURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"FlagDB.sqlite"];
// user data is in the application directory
NSURL *userDataURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"UserData.sqlite"];
NSManagedObjectModel *mom = self.managedObjectModel;
NSError *error = nil;
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData" URL:itemURL options:nil error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
...
Это прерывается с "Модель, используемая для открытия магазина, несовместима с той, которая использовалась для создания магазина". Сравнение хэшей в модели с хэшами в хранилище показывает, что они идентичны для сущностей, которые находятся в конфигурации ItemData.
Если я попытаюсь выполнить упрощенную миграцию, например так:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData" URL:itemURL options:options error:&error])
Произойдет сбой с «NSInvalidArgumentException», причина: «Модель не содержит конфигурацию «ItemData». Я предполагаю, что это связано с тем, что в процессе упрощенной миграции создается новая модель, которая не содержит моей конфигурации.
Основываясь на некоторых предложениях в других темах, я попытался выполнить упрощенную миграцию без конфигурации, а затем создать нового координатора, используя конфигурацию. Этот вид работает, но он добавляет таблицы в мой предварительно загруженный файл .sqlite, соответствующие объектам пользовательских данных (которые ему не принадлежат), и создает как предварительно загруженные таблицы данных, так и таблицы пользовательских данных во вновь созданном хранилище пользовательских данных. .Конечным результатом является сбой выборки, по-видимому, потому, что они ищут не в том магазине.
NSDictionary *migrationOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
// make a temp persistent store coordinator to handle the migration
NSPersistentStoreCoordinator *tempPsc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
// migrate the stores
if (![tempPsc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:itemURL options:migrationOptions error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
if (![tempPsc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:userDataURL options:migrationOptions error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
// make a permanent store coordinator
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSDictionary *readOnlyOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSReadOnlyPersistentStoreOption, nil];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData" URL:itemURL options:readOnlyOptions error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
/*if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"UserData" URL:userDataURL options:nil error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}*/
А затем позже...
OSAppDelegate *delegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = delegate.managedObjectContext;
// sanity check
for (NSPersistentStore *store in context.persistentStoreCoordinator.persistentStores) {
NSLog(@"store %@ -> %@", store.configurationName, store.URL);
NSMutableArray *entityNames = [[NSMutableArray alloc] init];
for (NSEntityDescription *entity in [context.persistentStoreCoordinator.managedObjectModel entitiesForConfiguration:store.configurationName]) {
[entityNames addObject:entity.name];
}
NSLog(@"entities: %@", entityNames);
}
NSFetchRequest *categoryFetchRequest = [[NSFetchRequest alloc] init];
categoryFetchRequest.entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:context];
categoryFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", categoryName];
NSError *error = nil;
Category *category = [[delegate.managedObjectContext executeFetchRequest:categoryFetchRequest error:&error] lastObject];
Это работает нормально, возвращая объект категории с соответствующим названием, пока я не раскомментирую добавление второго хранилища. Если я это сделаю, результат выборки вернется пустым. Диагностические сообщения NSLog печатают именно то, что я ожидаю. Каждое хранилище связано с правильной конфигурацией, и каждая конфигурация имеет соответствующие объекты.
Может ли кто-нибудь указать мне исходный код рабочей установки с несколькими магазинами или подсказать, что я делаю неправильно? Заранее спасибо!
РЕШЕНО: Суть проблемы заключалась в двух строках, помеченных НЕПРАВИЛЬНО в первом листинге кодов. Я пытался создавать конфигурации программно, но этого оказалось недостаточно. Если после этого вы запросите у ManagedObjectModel конфигурации, вы действительноувидите конфигурации в списке, и с этими конфигурациями будут связаны правильные объекты. Однако кажется, что нужно сделать что-то еще, чтобы PersistentStoreCoordinator мог правильно их использовать. Создание конфигураций в Xcode заставляет их работать.
ПРОДОЛЖЕНИЕ: Есть дополнительная загвоздка. Решение запуска отдельного прохода миграции перед настройкой окончательного координатора постоянного хранилища отлично работает... в симуляторе. На реальном устройстве разрешения более строгие. Если вы попытаетесь выполнить эту миграцию, произойдет сбой, поскольку хранилище в комплекте приложений доступно только для чтения. Миграция кажется необходимой, если вы не консолидируете свои модели.Если у вас есть только одна модель и магазин в комплекте приложений совместим с ней, миграция не требуется, и доступ с использованием конфигураций, определенных в Xcode, работает.
Другим вариантом может быть перемещение данных в каталог «Документы» перед попыткой переноса. Я не проверял, что этот подход работает.