Как эффективно объединить два постоянных хранилища основных данных iOS? - PullRequest
10 голосов
/ 02 апреля 2012

В нашем разрабатываемом приложении для хранения наших данных мы используем Core Data с резервным хранилищем sqlite.Объектная модель для нашего приложения сложна.Кроме того, общий объем данных, обслуживаемых нашим приложением, слишком велик, чтобы поместиться в пакет приложений для iOS (iPhone / iPad / iPod Touch).Из-за того, что наши пользователи, как правило, заинтересованы только в подмножестве данных, мы разбили наши данные таким образом, что приложение поставляется с подмножеством (хотя и ~ 100 МБ) объектов данных вкомплект приложений.Наши пользователи имеют возможность загружать дополнительные объекты данных (размером ~ 5–100 МБ) с нашего сервера после оплаты дополнительного содержимого через покупки в приложении iTunes.Файлы инкрементных данных (существующие в хранилищах sqlite) используют ту же версию xcdatamodel, что и данные, которые поставляются вместе с пакетом;в объектной модели изменений нет.Файлы инкрементных данных загружаются с нашего сервера в виде сжатых файлов sqlite.Мы не хотим раздувать наш пакет приложений, отправляя добавочное содержимое вместе с приложением.Кроме того, мы не хотим полагаться на запросы через веб-сервис (из-за сложной модели данных).Мы проверили загрузку инкрементных данных sqlite с нашего сервера.Мы смогли добавить загруженное хранилище данных в общий persistentStoreCoordinator приложения.

{
       NSError *error = nil;
       NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, 
                                [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

       if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:defaultStoreURL options:options error:&error])
       {            
           NSLog(@"Failed with error:  %@", [error localizedDescription]);
           abort();
       }    

       // Check for the existence of incrementalStore
       // Add incrementalStore
       if (incrementalStoreExists) {
           if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:incrementalStoreURL options:options error:&error])
           {            
               NSLog(@"Add of incrementalStore failed with error:  %@", [error localizedDescription]);
               abort();
           }    
       }
 }

Однако есть две проблемы с этим:

  1. Результаты выборки данных (например, с NSFetchResultController) отображаются с данными из incrementalStoreURL, добавленными кконец данных из defaultStoreURL.
  2. Некоторые объекты дублируются.В нашей модели данных есть много объектов с данными только для чтения;они дублируются, когда мы добавляем второй persistentStore в persistentStoreCoordinator.

В идеале мы хотели бы, чтобы базовые данные объединяли графы объектов из двух постоянных хранилищ в одно (нет общих отношений между данными издва магазина на момент загрузки данных).Также мы хотели бы удалить дубликаты объектов.Ища в Интернете, мы увидели пару вопросов от людей, пытающихся сделать то же самое, что и мы - например, этот ответ и этот ответ .Мы прочитали блог Маркуса Зарры об импорте больших наборов данных в Core Data .Однако ни одно из решений, которые мы видели, не сработало для нас.Мы не хотим вручную читать и сохранять данные из добавочного хранилища в хранилище по умолчанию, так как считаем, что это будет очень медленно и подвержено ошибкам на телефоне.Есть ли более эффективный способ слияния?

Мы попытались решить эту проблему путем реализации миграции вручную следующим образом.Однако мы не смогли успешно добиться слияния.Мы не совсем понимаем решение, предложенное в ответах 1 и 2, упомянутых выше.В блоге Маркуса Зарры были рассмотрены некоторые проблемы, с которыми мы столкнулись в начале нашего проекта по импорту нашего большого набора данных в iOS.

{
       NSError *error = nil;
       NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, 
                                [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];        

       NSMigrationManager *migrator = [[NSMigrationManager alloc] initWithSourceModel:__managedObjectModel destinationModel:__managedObjectModel];
       if (![migrator migrateStoreFromURL:stateStoreURL
                                type:NSSQLiteStoreType 
                             options:options 
                    withMappingModel:nil
                    toDestinationURL:destinationStoreURL 
                     destinationType:NSSQLiteStoreType 
                  destinationOptions:nil 
                               error:&error])
       {
           NSLog(@"%@", [error userInfo]);
           abort();
       }
}

Кажется, что автор ответа 1 закончил читать свои данные из инкрементного хранилища исохранение в магазине по умолчанию.Возможно, мы неправильно поняли решение, предложенное в обеих статьях 1 и 2. Размер наших данных может помешать нам вручную читать и повторно вставлять наши инкрементные данные в хранилище по умолчанию.Мой вопрос: каков наиболее эффективный способ получить графы объектов из двух persistentStores (которые имеют одинаковую objectModel) для объединения в один persistentStore?

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

Ответы [ 3 ]

6 голосов
/ 06 апреля 2012

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

Например, в моей модели данных были сущности "Страна" и "Штат". Мне нужно было иметь только один экземпляр Country и State в моем графе объектов. Я хранил эти объекты в дополнительных хранилищах и создавал их только в хранилище по умолчанию. Я использовал Fetched Properties, чтобы свободно связать мой основной объектный граф с этими объектами. Я создал хранилище по умолчанию со всеми экземплярами сущностей в моей модели. Инкрементные хранилища либо не имели объектов, доступных только для чтения (т. Е. В моем случае страну и штат), либо запускали их, либо удаляли их после завершения создания данных.

Следующим шагом является добавление инкрементного хранилища в его собственный persistentStoreCoordinator (не то же самое, что координатор для хранилища по умолчанию, в которое мы хотим перенести все содержимое) во время запуска приложения.

Последний шаг - вызов метода migratePersistentStore в инкрементном хранилище для объединения его данных с основным (т.е. по умолчанию) хранилищем. Presto!

Следующий фрагмент кода иллюстрирует два последних шага, которые я упомянул выше. Я сделал эти шаги, чтобы выполнить настройку для объединения добавочных данных в основное хранилище данных.

{
    NSError *error = nil;
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, 
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:defaultStoreURL options:options error:&error])
    {            
        NSLog(@"Failed with error:  %@", [error localizedDescription]);
        abort();
    }    

    // Check for the existence of incrementalStore
    // Add incrementalStore
    if (incrementalStoreExists) {

        NSPersistentStore *incrementalStore = [_incrementalPersistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:incrementalStoreURL options:options error:&error];
        if (!incrementalStore)
        {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }    

        if (![_incrementalPersistentStoreCoordinator migratePersistentStore:incrementalStore
            toURL:_defaultStoreURL
            options:options
            withType:NSSQLiteStoreType
            error:&error]) 
        {
            NSLog(@"%@", [error userInfo]);
            abort();

        }

        // Destroy the store and store coordinator for the incremental store
        [_incrementalPersistentStoreCoordinator removePersistentStore:incrementalStore error:&error];
        incrementalPersistentStoreCoordinator = nil;
        // Should probably delete the URL from file system as well
        //
    }
}
1 голос
/ 03 апреля 2012

Миграция не требуется - миграция предназначена для внесения изменений в NSManagedObjectModel, а не в сами данные.

Что вам действительно нужно, так это координатор постоянного хранилища, управляющий двумя постоянными хранилищами.Это довольно сложно, но не слишком сложно, на самом деле.

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

Вот хорошая статья Маркуса Зарры

http://www.cimgf.com/2009/05/03/core-data-and-plug-ins/

1 голос
/ 02 апреля 2012

Причина, по которой ваша миграция не работает, в том, что управляемая объектная модель идентична.

Технически, вы говорите о «миграции данных», а не «миграции схемы».API переноса CoreData предназначен для переноса схемы, то есть обработки изменений в модели управляемых объектов.

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

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

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

В документации для NSFetchRequest есть API для определения объема ваших запросов:

https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html

...