iOS 11+ Как перенести существующие базовые данные в Shared App Group для использования в расширении? - PullRequest
0 голосов
/ 05 сентября 2018

Когда я создал приложение iOS 11 с использованием шаблона основных данных, оно автоматически сгенерировало следующий код в AppDelete.m.

synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"My_History"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // 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 parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                    */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }

    return _persistentContainer;
}

- (void)saveContext {
NSManagedObjectContext *context = self.persistentContainer.viewContext;
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
    // 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.
    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
    abort();
}

Я хотел бы добавить расширение Today и iMessage, которое обращается к истории в основных данных. Из того, что я прочитал, мне нужно перенести эти данные, если они существуют, в общий контейнер приложения. Как бы я это сделал?

Код в цели C.

Я читал другие вопросы, связанные с этим, но все они, по-видимому, были до того, как Apple изменила способ работы с основными данными, чтобы упростить его. Как вы можете видеть из моего кода, я никогда не указывал точное имя файла хранилища данных. Каждый пример, который я видел, имел что-то вроде «My_History.sqllite». Я даже не знаю, является ли моя база данных sql lite, она была просто создана с помощью этого кода.

Ответы [ 3 ]

0 голосов
/ 17 сентября 2018

Я закончил тем, что получил следующее. Файл sqlite был фактически именем моего init плюс .sqlite в конце.

+ (NSPersistentContainer*) GetPersistentContainer {
    //Init the store.
    NSPersistentContainer *_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"Test_App"];

    //Define the store url that is located in the shared group.
    NSURL* storeURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.Test_App"] URLByAppendingPathComponent:@"Test_App.sqlite"];

    //Determine if we already have a store saved in the default app location.
    BOOL hasDefaultAppLocation = [[NSFileManager defaultManager] fileExistsAtPath: _persistentContainer.persistentStoreDescriptions[0].URL.path];

    //Check if the store needs migration.
    BOOL storeNeedsMigration = hasDefaultAppLocation && ![_persistentContainer.persistentStoreDescriptions[0].URL.absoluteString isEqualToString:storeURL.absoluteString];

    //Check if the store in the default location does not exist.
    if (!hasDefaultAppLocation) {
        //Create a description to use for the app group store.
        NSPersistentStoreDescription *description = [[NSPersistentStoreDescription alloc] init];

        //set the automatic properties for the store.
        description.shouldMigrateStoreAutomatically = true;
        description.shouldInferMappingModelAutomatically = true;

        //Set the url for the store.
        description.URL = storeURL;

        //Replace the coordinator store description with this description.
        _persistentContainer.persistentStoreDescriptions = [NSArray arrayWithObjects:description, nil];
    }

    //Load the store.
    [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
        //Check that we do not have an error.
        if (error == nil) {
            //Check if we need to migrate the store.
            if (storeNeedsMigration) {
                //Create errors to track migration and deleting errors.
                NSError *migrateError;
                NSError *deleteError;

                //Store the old location URL.
                NSURL *oldStoreURL = storeDescription.URL;

                //Get the store we want to migrate.
                NSPersistentStore *store = [_persistentContainer.persistentStoreCoordinator persistentStoreForURL: oldStoreURL];

                //Set the store options.
                NSDictionary *storeOptions = @{ NSSQLitePragmasOption : @{ @"journal_mode" : @"WAL" } };

                //Migrate the store.
                NSPersistentStore *newStore = [_persistentContainer.persistentStoreCoordinator migratePersistentStore: store toURL:storeURL options:storeOptions withType:NSSQLiteStoreType error:&migrateError];

                //Check that the store was migrated.
                if (newStore && !migrateError) {
                    //Remove the old SQLLite database.
                    [[[NSFileCoordinator alloc] init] coordinateWritingItemAtURL: oldStoreURL options: NSFileCoordinatorWritingForDeleting error: &deleteError byAccessor: ^(NSURL *urlForModifying) {
                        //Create a remove error.
                        NSError *removeError;

                        //Delete the file.
                        [[NSFileManager defaultManager] removeItemAtURL: urlForModifying error: &removeError];

                        //If there was an error. Output it.
                        if (removeError) {
                            NSLog(@"%@", [removeError localizedDescription]);
                        }
                    }
                     ];

                    //If there was an error. Output it.
                    if (deleteError) {
                        NSLog(@"%@", [deleteError localizedDescription]);
                    }
                }
            }
        } else {
            // 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 parent directory does not exist, cannot be created, or disallows writing.
             * The persistent store is not accessible, due to permissions or data protection when the device is locked.
             * The device is out of space.
             * The store could not be migrated to the current model version.
             Check the error message to determine what the actual problem was.
             */
            NSLog(@"Unresolved error %@, %@", error, error.userInfo);
            abort();
        }
    }];

    //Return the container.
    return _persistentContainer;
}
0 голосов
/ 13 июля 2019

solidsnake4444 ответ спасает мой день. Вот версия Swift 5.0.

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "MyApp")
    let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.my.app")!.appendingPathComponent("MyApp.sqlite")

    var defaultURL: URL?
    if let storeDescription = container.persistentStoreDescriptions.first, let url = storeDescription.url {
        defaultURL = FileManager.default.fileExists(atPath: url.path) ? url : nil
    }

    if defaultURL == nil {
        container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
    }
    container.loadPersistentStores(completionHandler: { [unowned container] (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }

        if let url = defaultURL, url.absoluteString != storeURL.absoluteString {
            let coordinator = container.persistentStoreCoordinator
            if let oldStore = coordinator.persistentStore(for: url) {
                do {
                    try coordinator.migratePersistentStore(oldStore, to: storeURL, options: nil, withType: NSSQLiteStoreType)
                } catch {
                    print(error.localizedDescription)
                }

                // delete old store
                let fileCoordinator = NSFileCoordinator(filePresenter: nil)
                fileCoordinator.coordinate(writingItemAt: url, options: .forDeleting, error: nil, byAccessor: { url in
                    do {
                        try FileManager.default.removeItem(at: url)
                    } catch {
                        print(error.localizedDescription)
                    }
                })
            }
        }
    })
    return container
}()
0 голосов
/ 06 сентября 2018

UPDATE:

Для переноса существующего постоянного хранилища NSPersistentContainer содержит persistentStoreCoordinator, экземпляр NSPersistentStoreCoordinator. Это предоставляет метод migratePersistentStore:toURL:options:withType:error: для миграции постоянного хранилища.

Я бы сделал следующее:

// Get the reference to the persistent store coordinator
let coordinator = persistentContainer.persistentStoreCoordinator
// Get the URL of the persistent store
let oldURL = persistentContainer.persistentStoreDescriptions.url
// Get the URL of the new App Group location
let newURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("YOUR_APP_GROUP")
// Get the reference to the current persistent store
let oldStore = coordinator.persistentStore(for: oldURL)
// Migrate the persistent store
do {
   try coordinator.migratePersistentStore(oldStore, to: newURL, options: nil, withType: NSSQLiteStoreType)
} catch {
   // ERROR
}

Обратите внимание, что вышеперечисленное не было проверено, и я не обрабатывал дополнительные, поэтому он не завершен Кроме того, я прошу прощения за это в Свифте. Надеюсь, вам достаточно легко написать эквивалент в Objective-C.

ОРИГИНАЛ:

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

NSPersistentContainer выставляет defaultDirectoryURL и заявляет:

Этот метод возвращает зависящий от платформы NSURL, при котором постоянный Магазин (ы) будет расположен или в настоящее время находятся. Этот метод может быть переопределено в подклассе NSPersistentContainer.

Если вы создаете подкласс NSPersistentContainer и определяете defaultDirectoryURL как каталог группы приложений, используя containerURLForSecurityApplicationGroupIdentifier, вы сможете получить доступ к контейнеру между вашим приложением и расширениями (при условии, что они имеют одинаковые права для группы приложений).

NSPersistentContainer также предоставляет persistentStoreDescriptions, который также содержит экземпляр URL. Аналогично, вы можете обновить его до URL группы приложений перед вызовом loadPersistentStoresWithCompletionHandler:.

Обратите внимание, что я не использовал NSPersistentContainer и не знаю, вызовет ли это совместное использование какие-либо проблемы с параллелизмом.

...