Использование нескольких Persistent Store в Core Data

в 4:19, , рубрики: cocoa touch, core data, objective-c, Программирование, разработка под iOS, метки: , ,

Всем iOS (да и MAC OS X) разработчикам известен такой системный фреймворк как Core Data. Эта штуковина представляет собой достаточно мощную ORM (по крайней мере для мобильной платформы).

Изначально в нашем приложении использовалась одна база данных для всей информации, которую нужно было сохранять в приложении. Но по мере разбухания функциональности стало понятно, что некоторые сущности логичнее расположить в разных базах, или даже в разных типах хранилищ (persistent store). Не буду углубляться в подробности, главное — что изначально монолитное NSSQLiteStore нужно было разделить на несколько.

В документации этот вопрос освещен очень бедно. Сказано лишь, что есть возможность пользоваться несколькими NSPersistentStore внутри одного NSPersistentStoreCoordinator'а, и даже более того, использовать их прямо из одного NSManagedObjectContext. Выглядит фантастикой.
Отчаявшись разобраться в вопросе исключително по эппловской документации пришлось переключится на гугл. Побродив по просторам сети, главным образом в районе Stack Overflow, я насобирал много кусочков информации, и после нескольких часов войны с XCode добился более менее рабочего решения. В ходе изучения вопроса выяснилось следущее:

Действительно, в одном NSManagedObjectContext может собираться информация из нескольких NSPersistentStore. Об этом есть подробная статья, которая описывает механизм подключения плагинов к приложению с Core Data. Ознакомиться можно тут: www.cimgf.com/2009/05/03/core-data-and-plug-ins/

Одному NSManagedObjectContext может соответствовать только одна NSManagedObjectModel. Как же указать какие сущности в каком хранилище должны хранится? Это делается с помощью имени конфигурации. Можно создать набор конфигураций в одной NSManagedObjectModel, а можно взять несколько NSManagedObjectModel и смерджить их в одну (при этом при создании каждой по прежнему нужно создавать именованную конфигурацию).

-(NSManagedObjectModel*)managedObjectModel;
{
  if(managedObjectModel)return managedObjectModel;
 
  NSBundle*myBundle =[NSBundle bundleForClass:[self class]];
  NSArray*bundles =[NSArray arrayWithObject:myBundle];
  managedObjectModel =[[NSManagedObjectModel mergedModelFromBundles:bundles] retain];
  return managedObjectModel;
}

После того как у нас есть подготовленная (смердженная или заранее созднанная) NSManagedObjectModel, можно добавлять хранилища в координатор с помощью функции

- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:(NSString *)configuration URL:(NSURL *)storeURLoptions:(NSDictionary *)options error:(NSError **)error

используя имя конфигурации для каждого типа хранилища. Примерно так:

 NSPersistentStore * store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 
                                              configuration:CONFIG_NAME
                                                        URL:databaseURL
                                                    options:options
                                                      error:&error];

Файл с хранилищем создастся автоматически. После того как цель была достигнута уж очень хотелось своими глазами посмотреть на хранилища которые Core Data там насоздавала. Конкретно под лупой рассматривался NSSQLiteStore. Выяснилась интересная вещь — каким бы не было имя конфигурации для файла, в нем все равно создаются таблички для всех сущностей из object model. Однако же сущности в файл пишутся именно те, которые указаны в конфигурации. В остальном все магическим образом заработало, из одного NSManagedObjectModel сущности действительно распихивались по разным базам!

Отдельно нужно сказать про связь хранилищ. Нельзя создавать relationships между сущностями из разных хранилищ. Но можно использовать fetched properties. Это вобщем то более менее понятно описано в эппловской доке по адресу developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW5

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

- (BOOL)migratePersistentStore:(NSURL *)sourceStoreURL withType:(NSString *)sourceType to:(NSURL *)destinationStoreURL type:(NSString *)destinationType andAddWithConfiguration:(NSString *)configuration
{
    NSError *error = nil;
    
    NSDictionary *sourceMetadata = [self.class metadataForPersistentStoreOfType:sourceType URL:sourceStoreURL error:&error];
    
    if(sourceMetadata==nil)
        return NO;
    
    NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata];
    
    NSMigrationManager *migrationManager = [[[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:self.managedObjectModel] autorelease];
    
    NSMappingModel *mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:self.managedObjectModel];
    
    if(mappingModel==nil)
        return NO;
    
    BOOL result = [migrationManager migrateStoreFromURL:sourceStoreURL type:sourceType options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:destinationType destinationOptions:nilerror:&error];
    
    if(result==NO)
        return NO;
    
    NSLog(@"Successful DB migration");
    
    NSDictionary *options = [self.class migrationOptionsWithAutoMigration:NO];
    
    NSPersistentStore * addedStore = [self addPersistentStoreWithType:destinationType configuration:configuration URL:destinationStoreURL options:options error:&error];
        
    return addedStore!= nil;
}

К сожалению разложить таким способом одну базу на несколько не получилось. Первый вызов метода отрабатывает на ура. Однако на втором вызове, уже с другим именем конфигурации, Core Data трагично сообщает мне «Can't add the same store twice». В итоге я решил смигрировать только ту конфигурацию, где хранятся важные данные, а базы под всякие логи и кэши, которые можно перестроить заново, создавать с нуля.

Автор: ocom

* - обязательные к заполнению поля