Миграция данных при включении/выключении iCloud


Локальный счет

Из сеанса WWDC 2013 207 Об основных данных и iCloud:

Вы предоставляете нам один URL-адрес магазина внутри локального приложения. песочница, а затем мы создаем непрозрачный контейнер с записью внутри это для каждой учетной записи в системе, включая локальную учетную запись, которая это наш термин для того, что происходит, когда нет учетной записи iCloud на система. Это специальное хранилище, управляемое основными данными, чтобы тебе и не нужно этого делать. сделайте что-нибудь особенное, потому что ваш пользователь не имеет учетная запись iCloud.

В iOS 7/OS X 10.9 основные данные с iCloud автоматически используют локальную учетную запись для ситуаций, в которых iCloud выключен. В отличие от резервного хранилища (используется, когда iCloud включен, но недоступен), локальная учетная запись будет полностью заменена учетной записью iCloud, когда служба включена, без какого-либо слияния. Данные в локальной учетной записи доступны только в том случае, если iCloud выключен. Этот случается, когда:

  • нет учетной записи iCloud.
  • есть учетная запись iCloud, но "документы и данные" отключена.
  • есть учетная запись iCloud, но приложение было отключено в разделе "Документы и данные".
Вышеизложенное-это то, что я понял из экспериментов. Пожалуйста, поправьте меня, если я ошибаюсь.

Когда данные исчезают

Используется как есть, локальный опыт пользователя учетной записи ужасен . Если вы добавляете данные в приложение с помощью iCloud выключен, а затем включите его, данные "исчезнут", и вы можете подумать, что они были удалены. Если вы добавите данные в приложение с включенным iCloud, а затем выключите его, данные также "исчезнут".

Я видел примеры, которые пытаются обойти это, добавляя (дополнительные) настройки iCloud в приложение и управляя своим собственным" локальным " магазином (не тем, который предоставляет iCloud). Для меня это пахнет дублированием работы.

Использование локальной учетной записи для миграции данных

Как насчет этого подходить?

  • Всегда используйте основные данные и iCloud, независимо от того, включен или выключен iCloud.
  • когда iCloud перейдет из режима off в режим on, спросите пользователей, хотят ли они объединить локальную учетную запись с учетной записью iCloud. Если да, то объедините, удалите дубликаты с приоритетом local и очистите локальную учетную запись.
  • когда iCloud переходит от включения к выключению, спросите пользователей, хотят ли они объединить магазин iCloud с локальной учетной записью. Если да, объедините и удалите дубликаты приоритезации iCloud.

Это похоже на то, что делают напоминания. Однако Reminders запрашивает пользователя о переносе данных непосредственно из настроек iCloud, чего не могут сделать американские разработчики.

Вопросы

1) Есть ли у этого подхода какие-либо недостатки или пограничные случаи, которые могут быть неочевидны на первый взгляд? Возможно, мы не должны использовать локальную учетную запись, созданную iCloud, как это.

2) являются NSPersistentStoreCoordinatorStoresWillChangeNotification и NSPersistentStoreCoordinatorStoresDidChangeNotification достаточными для обнаружения всех можно ВКЛ на Выкл и выкл на iCloud переходы?

3) Вы бы сделали запрос пользователя и слияние между NSPersistentStoreCoordinatorStoresWillChangeNotification и NSPersistentStoreCoordinatorStoresDidChangeNotification, или собрали бы всю информацию в них и ждали, пока хранилище не будет изменено? Я спрашиваю, потому что эти уведомления, по-видимому, отправляются в фоновом режиме, и блокировка их для выполнения потенциально длительной операции может быть не тем, что ожидают основные данные.
2 7

2 ответа:

Я думаю, что вы неправильно поняли то, что было сказано на сессии 207.

Основные данные не будут автоматически создавать локальное и хранилище iCloud для вас, а не те, которые будут синхронизировать данные, когда учетная запись iCloud будет выключена в любом случае. В зависимости от того, что выбрал пользователь, вы должны создать магазин либо с помощью опции NSPersistentStoreUbiquityNameKey (для магазина iCloud), либо без нее (для локального магазина).

Потому что Настройка безопасности по умолчанию для новых приложений При первой установке приложения вы должны спросить пользователя, хочет ли он использовать iCloud или нет. Попробуйте это с приложением Pages от Apple.

Если пользователь впоследствии изменит настройки предпочтений, ваше приложение должно перенести магазин в iCloud или из него.

Часть Core Data обрабатывает автоматически, если вы переключаете учетную запись iCloud (выход и вход с другой учетной записью), то приложение будет работать с любым хранилищем данных Core, которое могло быть создано во время входа в систему. этот счет.

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

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

Конечно, вы можете вызвать NSManagedObjectContext save и Сброс NSManagedObjectContext.

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

Теперь давайте поговорим об этом немного подробнее.

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

Это означает, что, хотя изменения вашего пользователя не будут внесены в iCloud немедленно, если они когда-нибудь снова зарегистрируются, они будут там и ждать.

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

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

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

И немного дальше

Мы также представляем новую опцию, которая поможет вам создавать резервные копии или локальные копии постоянного хранилища iCloud , называемого NSPersistentStore Удалить Опцию Вездесущих Метаданных.

Это удаляет все связанные метаданные из хранилища iCloud; что средства, все, что мы записываем в словарь метаданных, а также сам файл хранилища, и это очень важно, если вы хотите использовать миграция API для создания резервных копий или локальной копии в долговременном хранилище вы хотите открыть без параметров iCloud .

Также взгляните на эту ссылку на ошибку в книге Тима Роудли

Http://timroadley.com/2014/02/13/learning-core-data-for-ios-errata/

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

Я опубликовал пример приложения, которое делает все это здесь. Взгляните на видео, чтобы увидеть ожидаемое поведение. http://ossh.com.au/design-and-technology/software-development/

Некоторые особенности примеры приложений включают в себя:

особенности включают в себя:

  • пример iOS и OSX основные приложения для обработки данных с интеграцией iCloud
  • использование местный или iCloud хранилище основных данных
  • включает в себя комплект настроек (обратите внимание, что это создает страницу настроек в приложении Настройки), которая включает:
    • использовать iCloud настройка (или Выкл.)
    • сделайте резервную копию настройка (on или off)
    • отображение приложения версия и номер сборки
  • запрашивает пользователя о параметрах хранения если параметр Use iCloud изменен на ON
  • перенос основного хранилища данных в iCloud и обратно в зависимости от настройки предпочтений пользователей и ответа на запросы
  • обнаруживает удаление магазина iCloud с другого устройства и очищает, создавая новое пустое хранилище iCloud
  • проверяет наличие существующих файлов iCloud при миграции локальное хранилище в iCloud и предлагает пользователю объединить или удалить данные в локальном хранилище, если файл iCloud существует
  • делает резервную копию еслисделать резервную копию предпочтительно установить значение ВКЛ.  Имя файла резервной копии: persistentStore_Backup_yyyy_MM_dd_hh_mm_ss. чтобы использовать его:
    • установите параметр резервного копирования ON, и в следующий раз, когда приложение будет активировано, оно сделает резервную копию текущего хранилища данных ядра и сбросит параметр OFF
    • файл можно скопировать на ПК или Mac из iTunes
    • для восстановления просто установите приложение на использование локальных файлов (используйте iCloud preference OFF) и замените файл persistentStore на необходимый файл резервной копии (обратите внимание, что файл должен быть вызван persistentStore ).
  • редактирование запись и сохранение/отмена правок в детальном представлении
  • асинхронное открытие основного хранилища данных чтобы гарантировать, что длительные миграции не блокируют основной поток и не приводят к завершению работы приложения
  • загрузка данных в фоновом потоке С помощью Pull для обновления в main UITableView для запуска другого фонового потока (вы можете запустить несколько фоновых потоков одновременно будьте осторожны!)
  • отображение связанных объектов в detailView использование UITableView, fetchedResultsController и предиката для фильтрации выбора
  • загрузите исходные данные, если хранилище уже не существует, проверяет, был ли файл iCloud создан другим устройством
  • индикатор загрузки iCloud, индикатор сетевой активности включается, когда необходимо вести журналы транзакций основных данных. синхронизированы, заняты синхронизацией, импортируются или когда выполняются фоновые задачи
  • интерфейс стиля боковой панели с несколькими основными и подробными представлениями для приложений iOS и OS X
  • диспетчер резервных копий файлов который позволяет создавать резервные копии, копировать резервные файлы в iCloud и из iCloud, отправлять и получать резервные файлы по электронной почте и восстанавливать из файла резервной копии.

Я попытаюсь ответить на свой собственный вопрос, отчасти для того, чтобы организовать мои мысли и частично ответить @DuncanGroenewald.

1) имеет ли этот подход какие-либо недостатки или пограничные случаи, которые могли бы не бросается в глаза с первого взгляда?

Да. Локальное хранилище и хранилище учетных записей iCloud управляются основными данными и могут быть удалены в любое время.

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

Что касается хранилищ учетных записей iCloud, я вижу два сценария, в которых они могут быть удалены: а) чтобы освободить место после того, как пользователь выключил iCloud, или Б) потому что пользователь запросил его, выбрав Настройки > iCloud > удалить все.

Если пользователь запросил его, то вы можете возразить, что миграция данных не является проблемой.

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

2) являются NSPersistentStoreCoordinatorStoreswillchangenotification и NSPersistentStoreCoordinatorStoresdidchangenotification достаточно для обнаружить все возможные переходы от включения к выключению и от включения к включению iCloud?

Да. Он требует, чтобы вы всегда создавали постоянное хранилище с помощью NSPersistentStoreUbiquitousContentNameKey, независимо от того, включен или выключен iCloud. Вот так:

[self.managedObjectContext.persistentStoreCoordinator
 addPersistentStoreWithType:NSSQLiteStoreType
 configuration:nil
 URL:storeURL
 options:@{ NSPersistentStoreUbiquitousContentNameKey : @"someName" }
 error:&error];
На самом деле, достаточно только слушать NSPersistentStoreCoordinatorStoresDidChangeNotification (как показано ниже). Это будет называться когда хранилище добавляется при запуске или изменяется во время выполнения.

3) Вы бы сделали запрос пользователя и слияние между NSPersistentStoreCoordinatorStoreswillchangenotification и NSPersistentStoreCoordinatorStoresdidchangenotification, или собрать все информацию в них и ждать, пока магазин не изменится? - Спрашиваю я. поскольку эти уведомления, как представляется, отправляются в фоновом режиме, и блокирование их для выполнения потенциально длительной операции может оказаться невозможным. какие основные данные рассчитывать.

Вот как бы я это сделал в NSPersistentStoreCoordinatorStoresDidChangeNotification.

Поскольку это уведомление отправляется как при запуске, так и при изменении магазина во время выполнения, мы можем использовать его для сохранения текущего url-адреса магазина и маркера идентификации ubiquity (если таковой имеется).

Затем мы проверяем, находимся ли мы в сценарии включения/выключения перехода и соответственно переносим данные.

Для краткости я не включаю никакой пользовательский код, пользовательские подсказки или обработку ошибок. Вы должны спросить (или, по крайней мере, сообщить) пользователя прежде чем делать какие-либо миграции.

- (void)storesDidChange:(NSNotification *)notification
{
    NSDictionary *userInfo = notification.userInfo;
    NSPersistentStoreUbiquitousTransitionType transitionType = [[userInfo objectForKey:NSPersistentStoreUbiquitousTransitionTypeKey] integerValue];
    NSPersistentStore *persistentStore = [userInfo[NSAddedPersistentStoresKey] firstObject];
    id<NSCoding> ubiquityIdentityToken = [NSFileManager defaultManager].ubiquityIdentityToken;

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if (transitionType != NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted) { // We only care of cases if the store was added or removed
        NSData *previousArchivedUbiquityIdentityToken = [defaults objectForKey:HPDefaultsUbiquityIdentityTokenKey];
        if (previousArchivedUbiquityIdentityToken) { // Was using ubiquity store
            if (!ubiquityIdentityToken) { // Changed to local account
                NSString *urlString = [defaults objectForKey:HPDefaultsPersistentStoreURLKey];
                NSURL *previousPersistentStoreURL = [NSURL URLWithString:urlString];
                [self importPersistentStoreAtURL:previousPersistentStoreURL
                 isLocal:NO
                 intoPersistentStore:persistentStore];
            }
        } else { // Was using local account
            if (ubiquityIdentityToken) { // Changed to ubiquity store
                NSString *urlString = [defaults objectForKey:HPDefaultsPersistentStoreURLKey];
                NSURL *previousPersistentStoreURL = [NSURL URLWithString:urlString];
                [self importPersistentStoreAtURL:previousPersistentStoreURL 
                 isLocal:YES 
                 intoPersistentStore:persistentStore];
            }
        }
    }
    if (ubiquityIdentityToken) {
        NSData *archivedUbiquityIdentityToken = [NSKeyedArchiver archivedDataWithRootObject:ubiquityIdentityToken];
        [defaults setObject:archivedUbiquityIdentityToken forKey:HPModelManagerUbiquityIdentityTokenKey];
    } else {
        [defaults removeObjectForKey:HPModelManagerUbiquityIdentityTokenKey];
    }
    NSString *urlString = persistentStore.URL.absoluteString;
    [defaults setObject:urlString forKey:HPDefaultsPersistentStoreURLKey];
    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI
    });
}

Затем:

- (void)importPersistentStoreAtURL:(NSURL*)importPersistentStoreURL 
    isLocal:(BOOL)isLocal 
    intoPersistentStore:(NSPersistentStore*)persistentStore 
{
    if (!isLocal) { 
        // Create a copy because we can't add an ubiquity store as a local store without removing the ubiquitous metadata first,
        // and we don't want to modify the original ubiquity store.
        importPersistentStoreURL = [self copyPersistentStoreAtURL:importPersistentStoreURL];
    }
    if (!importPersistentStoreURL) return;

    // You might want to use a different concurrency type, depending on how you handle migration and the concurrency type of your current context
    NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    importContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
    NSPersistentStore *importStore = [importContext.persistentStoreCoordinator
            addPersistentStoreWithType:NSSQLiteStoreType
            configuration:nil
            URL:importPersistentStoreURL
            options:@{NSPersistentStoreRemoveUbiquitousMetadataOption : @(YES)}
            error:nil];

    [self importContext:importContext intoContext:_managedObjectContext];
    if (!isLocal) {
        [[NSFileManager defaultManager] removeItemAtURL:importPersistentStoreURL error:nil];
    }
}

Миграция данных выполняется в importContext:intoContext. Эта логика будет зависеть от вашей модели и политики дублирования и конфликтов.

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