coredata — перейти к цели группы приложений

Я новичок в расширении Today и использую встроенную структуру.

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

Как я могу перенести нашу текущую версию в магазин приложений, чтобы иметь возможность перейти на новую структуру?


person Jason Hocker    schedule 02.12.2014    source источник


Ответы (4)


Если кому-то нужно быстрое решение, просто добавьте функцию в didFinishLaunchingWithOptions.

 func migratePersistentStore(){

    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    var storeOptions = [AnyHashable : Any]()
    storeOptions[NSMigratePersistentStoresAutomaticallyOption] = true
    storeOptions[NSInferMappingModelAutomaticallyOption] = true
    let oldStoreUrl = self.applicationDocumentsDirectory.appendingPathComponent("YourApp.sqlite")!
    let newStoreUrl = self.applicationGroupDirectory.appendingPathComponent("YourApp.sqlite")!
    var targetUrl : URL? = nil
    var needMigrate = false
    var needDeleteOld = false

    if FileManager.default.fileExists(atPath: oldStoreUrl.path){
        needMigrate = true
        targetUrl = oldStoreUrl
    }

    if FileManager.default.fileExists(atPath: newStoreUrl.path){
        needMigrate = false
        targetUrl = newStoreUrl

        if FileManager.default.fileExists(atPath: oldStoreUrl.path){
            needDeleteOld = true
        }
    }
    if targetUrl == nil {
        targetUrl = newStoreUrl
    }
    if needMigrate {
        do {
            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: targetUrl!, options: storeOptions)
            if let store = coordinator.persistentStore(for: targetUrl!) 
             {
                do {
                    try coordinator.migratePersistentStore(store, to: newStoreUrl, options: storeOptions, withType: NSSQLiteStoreType)

                } catch let error {
                    print("migrate failed with error : \(error)")
                }
            }
        } catch let error {
            CrashlyticsHelper.reportCrash(err: error as NSError, strMethodName: "migrateStore")
        }
    }
  if needDeleteOld {
        DBHelper.deleteDocumentAtUrl(url: oldStoreUrl)
        guard let shmDocumentUrl = self.applicationDocumentsDirectory.appendingPathComponent("NoddApp.sqlite-shm") else { return }
        DBHelper.deleteDocumentAtUrl(url: shmDocumentUrl)
        guard let walDocumentUrl = self.applicationDocumentsDirectory.appendingPathComponent("NoddApp.sqlite-wal") else { return }
        DBHelper.deleteDocumentAtUrl(url: walDocumentUrl)
    }
}

Мой PersistentStoreCoordinator выглядит так:

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationGroupDirectory.appendingPathComponent("YourApp.sqlite")
    var storeOptions = [AnyHashable : Any]()
    storeOptions[NSMigratePersistentStoresAutomaticallyOption] = true
    storeOptions[NSInferMappingModelAutomaticallyOption] = true
    var failureReason = "There was an error creating or loading the application's saved data."
    do {
        try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options:storeOptions)
    } catch {
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
        dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?

        dict[NSUnderlyingErrorKey] = error as NSError
        let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
        abort()
    }
    return coordinator
}()

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

Изменить. Для удаления файла из старого местоположения рекомендуется использовать NSFileCoordinator для выполнения задачи.

static func deleteDocumentAtUrl(url: URL){
    let fileCoordinator = NSFileCoordinator(filePresenter: nil)
    fileCoordinator.coordinate(writingItemAt: url, options: .forDeleting, error: nil, byAccessor: {
        (urlForModifying) -> Void in
        do {
            try FileManager.default.removeItem(at: urlForModifying)
        }catch let error {
            print("Failed to remove item with error: \(error.localizedDescription)")
        }
    })
}

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

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

person Pranav Gupta    schedule 29.11.2017

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

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

Конечно, вы можете просто включить модель в обе цели. Это будет означать, что у вас будет две копии файла, что приводит к пустой трате места. Хотя, наверное, места мало.

Для постоянного хранилища вам нужно настроить группу приложений и использовать файл хранилища в каталоге группы. Группы приложений — это один из параметров в разделе «Возможности» для приложения и расширения. Включите его и создайте имя группы. Затем поместите файл постоянного хранилища в групповой каталог, который вы можете найти с помощью кода, подобного

NSURL *groupURL = [[NSFileManager defaultManager]
    containerURLForSecurityApplicationGroupIdentifier:
        @"GROUP NAME HERE"];

[О некоторых из них я рассказываю более подробно в моем блоге].

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

  1. Проверьте, существует ли старая негрупповая копия данных
  2. Если это так, настройте стек Core Data, используя этот файл. Затем используйте migratePersistentStore:toURL:options:withType:error:, чтобы переместить его в новое место. Затем удалите старую копию.
  3. Если старой версии не существует, просто настройте Core Data с новой копией, как обычно.
person Tom Harrington    schedule 02.12.2014
comment
Я могу перенести свои старые данные в группу приложений (поскольку необходимо обмениваться данными между приложением и расширением), но я не могу найти файл .sqlite, который ранее был виден в каталоге документов, поскольку я перенес его в общую папку группы приложений. . Могу ли я найти этот файл, и если да, то как. Также я попытался создать копию приложения Group sqlite в каталоге моих документов, но он показывает мне только данные, которые были до миграции, любые новые строки, добавленные в основные данные после миграции, не видны, если я скопирую их в каталог документов. - person Arun Gupta; 27.01.2015
comment
Как вы пытаетесь найти этот файл SQLite? Какие ошибки (ошибки) вы получаете? Вероятно, вам следует задать новый вопрос об этом. - person Tom Harrington; 28.01.2015
comment
Я копирую файл из группы приложений в каталог документов с помощью copyItemAtPath, хотя я получаю файл, но все новые записи после миграции там не существуют - person Arun Gupta; 28.01.2015
comment
Вам нужно задать новый вопрос здесь и предоставить более подробную информацию, потому что слишком много информации, чтобы осветить ее в комментариях. - person Tom Harrington; 28.01.2015
comment
Также я столкнулся с одной проблемой: я могу получить доступ к общим основным данным как из приложения, так и из расширения действия приложения. Я могу импортировать заметки из приложения Notes в свое приложение, но когда я открываю свое приложение, я не вижу документ в своем приложении. Только когда я убиваю свое приложение и перезапускаю его снова, я могу видеть эти документы. Я использую другой контекст в своем приложении и расширении. Как мне это решить? - person Arun Gupta; 28.01.2015
comment
Вот новый вопрос, который я поднял: stackoverflow .com/questions/28197360/ - person Arun Gupta; 28.01.2015
comment
Как это будет «перенесено в фреймворк»? Я не знаком с созданием фреймворков в приложении, только с использованием внешних фреймворков. Я хотел бы услышать больше об этом. - person SAHM; 25.04.2017
comment
Кроме того, не могли бы вы указать мне где-нибудь в Интернете пример кода, который выполнит ваши инструкции в шагах 1-3? Я понял для себя, что это будет лучше всего сделать (изначально я думал о перемещении файлов перед настройкой стека), но я не уверен, где и когда это сделать. Я предполагаю, что это должно быть синхронно - может быть, сразу после создания NSPersistentStore (или NSPersistentContainer)? Если вы сделаете это неправильно, это будет иметь разрушительные последствия, поэтому, если вы можете дать немного более конкретное руководство, мы будем очень признательны. - person SAHM; 25.04.2017
comment
Как получить старый NSPersistentStore из NSPersistentContainer или NSPersistentCoordinator? coordinator.persistentStore(for: oldURL) кажется, что всегда возвращается ноль. Мне нужно знать, существует ли старый постоянный магазин, а затем, если он действительно передает постоянный магазин как параметр в migratePersistentStore:toURL:options:withType:error: - person Bocaxica; 09.08.2018

Для переноса старой части данных я проделал некоторую работу.

  1. как проверить существование старой базы данных или нет, я использую приведенный ниже код для проверки.

    if ([fileManager fileExistsAtPath:[storeURL path]]) {
    NSLog(@"old single app db exist.");
    targetURL = storeURL;
    needMigrate = true;
    }
    // storeURL is the store url return by:
    - (NSURL *)applicationDocumentsDirectory
    {
       return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    }
    
  2. Перенесите старые данные в новое место хранения данных.

Если старое хранилище данных существует, а хранилище групповых данных не существует, я использую приведенный ниже код для переноса:

if (needMigrate) {
    NSError *error = nil;
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [context setPersistentStoreCoordinator:__persistentStoreCoordinator];
    [__persistentStoreCoordinator migratePersistentStore:store toURL:groupURL options:options withType:NSSQLiteStoreType error:&error];
    if (error != nil) {
        NSLog(@"Error when migration to groupd url %@, %@", error, [error userInfo]);
        abort();
    }
}

флаг needMigrate устанавливается путем проверки наличия старых данных.

  1. если данных нет, я буду использовать групповой URL для создания PSC.

Для справки, я вставляю полный код здесь:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (__persistentStoreCoordinator != nil)
    {
       return __persistentStoreCoordinator;
    }

bool needMigrate = false;
bool needDeleteOld  = false;

NSString *kDbName = @"xxx.sqlite";

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:kDbName];

NSURL *groupURL = [[self applicationGroupDocumentDirectory] URLByAppendingPathComponent:kDbName];

NSURL *targetURL =  nil;

NSFileManager *fileManager = [NSFileManager defaultManager];

if ([fileManager fileExistsAtPath:[storeURL path]]) {
    NSLog(@"old single app db exist.");
    targetURL = storeURL;
    needMigrate = true;
}


if ([fileManager fileExistsAtPath:[groupURL path]]) {
       NSLog(@"group db exist");
       needMigrate = false;
       targetURL = groupURL;

    if ([fileManager fileExistsAtPath:[storeURL path]]) {
        needDeleteOld = true;
    }
}

if (targetURL == nil)
    targetURL = groupURL;

NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: @(YES),
                            NSInferMappingModelAutomaticallyOption: @(YES)};

NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];


NSPersistentStore *store;
store = [__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:targetURL options:options error:&error];

if (!store)
{
    /*
     Replace this implementation with code to handle the error appropriately.

     abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

     Typical reasons for an error here include:
     * The persistent store is not accessible;
     * The schema for the persistent store is incompatible with current managed object model.
     Check the error message to determine what the actual problem was.


     If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.

     If you encounter schema incompatibility errors during development, you can reduce their frequency by:
     * Simply deleting the existing store:
     [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]

     * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
     [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

     Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.

     */
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

// do the migrate job from local store to a group store.
if (needMigrate) {
    NSError *error = nil;
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [context setPersistentStoreCoordinator:__persistentStoreCoordinator];
    [__persistentStoreCoordinator migratePersistentStore:store toURL:groupURL options:options withType:NSSQLiteStoreType error:&error];
    if (error != nil) {
        NSLog(@"Error when migration to groupd url %@, %@", error, [error userInfo]);
        abort();
    }
}

return __persistentStoreCoordinator;
}

/**
 Returns the URL to the application's Documents directory.
 */
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

- (NSURL *)applicationGroupDocumentDirectory
{
return [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.kzjeef.shitf.scheduler"];
}
person Jiejing Zhang    schedule 07.06.2015
comment
В Swift, по умолчанию, persistStoreCoordinator создается как ленивая переменная, которая, похоже, заставляет этот код выполняться сто раз. Где он пытается открыть файлы, чтобы проверить их существование, и в конечном итоге выдает ошибку, когда открывается слишком много файлов. - person Ace Green; 06.09.2016
comment
В итоге сделал это во время запуска приложения, чтобы избежать гонки. Это было очень полезно, ура. - person Ace Green; 07.09.2016

Для миграции в swift 3.0 и выше просто замените ниже метод persistenceStoreCoordinator своим

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {

    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let options = [
        NSMigratePersistentStoresAutomaticallyOption: true,
        NSInferMappingModelAutomaticallyOption: true
    ]

    let oldStoreUrl = self.applicationDocumentsDirectory.appendingPathComponent("TaskTowerStorage.sqlite")
    let directory: NSURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppGroupID)! as NSURL
    let newStoreUrl = directory.appendingPathComponent("YourDatabaseName.sqlite")!

    var targetUrl : URL? = nil
    var needMigrate = false
    var needDeleteOld = false


    if FileManager.default.fileExists(atPath: oldStoreUrl.path){
        needMigrate = true
        targetUrl = oldStoreUrl
    }
    if FileManager.default.fileExists(atPath: newStoreUrl.path){
        needMigrate = false
        targetUrl = newStoreUrl

        if FileManager.default.fileExists(atPath: oldStoreUrl.path){
            needDeleteOld = true
        }
    }
    if targetUrl == nil {
        targetUrl = newStoreUrl
    }

    if needMigrate {
        do {
            try coordinator?.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: targetUrl!, options: options)
            if let store = coordinator?.persistentStore(for: targetUrl!)
            {
                do {
                    try coordinator?.migratePersistentStore(store, to: newStoreUrl, options: options, withType: NSSQLiteStoreType)

                } catch let error {
                    print("migrate failed with error : \(error)")
                }
            }
        } catch let error {
            //CrashlyticsHelper.reportCrash(err: error as NSError, strMethodName: "migrateStore")
            print(error)
        }
    }

    if needDeleteOld {
        self.deleteDocumentAtUrl(url: oldStoreUrl)
        self.deleteDocumentAtUrl(url: self.applicationDocumentsDirectory.appendingPathComponent("YourDatabaseName.sqlite-shm"))
        self.deleteDocumentAtUrl(url: self.applicationDocumentsDirectory.appendingPathComponent("YourDatabaseName.sqlite-wal"))
    }

    do {
        try coordinator!.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: targetUrl, options: options)
    } catch var error as NSError {
        coordinator = nil
        NSLog("Unresolved error \(error), \(error.userInfo)")
        abort()
    } catch {
        fatalError()
    }
    return coordinator

}() 
     lazy var applicationDocumentsDirectory: URL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named 'Bundle identifier' in the application's documents Application Support directory.
    let urls = Foundation.FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return urls[urls.count-1]
}()


lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = Bundle.main.url(forResource: "StorageName", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
person Bucket    schedule 08.12.2017