Подтвердить что ты не робот

Как лучше всего управлять восстановлением штата CoreData + iOS?

Я пытаюсь добавить iOS 6 State Restoration в приложение, с которым я только что закончил. Это приложение, в котором модель в основном поступает из CoreData.

В качестве рекомендуется, я использую подход "pass the baton" для перемещения контекстов управляемых объектов между View Controllers - я создаю MOC в мой делегат приложения, передайте его первому диспетчеру просмотра, который передает его второму в файле prepareForSegue:, который передает его третьему в prepareForSegue:, и т.д.

Это, похоже, не очень хорошо справляется с государственной реставрацией. Единственное, что я могу придумать, - это получить MOC из моего App Delegate непосредственно в реализации viewControllerWithRestorationIdentifierPath: coder:. На самом деле, похоже, что разработчики Apple сделали что-то подобное, наблюдая за сессией WWDC.

Это лучший/единственный способ? Действительно ли восстановление состояния эффективно нарушает Pass-The-Baton, по крайней мере, для восстановленных контроллеров вида?

4b9b3361

Ответ 1

Чтобы ознакомиться с государственным восстановлением, я настоятельно рекомендую сессию WWDC 2013 "Что нового в государственном восстановлении". Хотя восстановление состояния было введено годом ранее в iOS 6, iOS 7 принесла некоторые заметные изменения.

Передача вперед

Используя подход "передать эстафету", в какой-то момент создается корневой NSManagedObjectContext и присоединяется NSPersistentStoreCoordinator. Контекст передается контроллеру представления, а последующие дочерние контроллеры представления, в свою очередь, передаются этому корневому контексту или дочернему контексту.

Например, когда пользователь запускает приложение, создается корневой NSManagedObjectContext который передается в контроллер корневого представления, который управляет NSFetchedResultsController. Когда пользователь выбирает элемент в контроллере представления, создается новый контроллер подробного представления и передается экземпляр NSManagedObjectContext.

Passing the Managed Object Context Baton

Сохранение и восстановление государства

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

State Restoration

Когда это происходит, он не использует никаких пользовательских инициализаторов, сегментов и т. UIStateRestoring Протокол UIStateRestoring определяет методы, используемые для кодирования и декодирования состояния, которые допускают некоторую степень настройки. Объекты, которые соответствуют NSCoding могут храниться в архивах восстановления, а в iOS 7 восстановление состояния было распространено на объекты модели и источники данных.

Состояние восстановления предназначено для хранения только той информации, которая требуется для восстановления видимого состояния приложения. Для приложения Core Data это означает хранение информации, необходимой для определения местоположения объекта в правильном постоянном хранилище.

На первый взгляд это кажется простым. В случае контроллера представления, управляющего NSFetchedResultsController это может означать хранение дескрипторов предикатов и сортировки. Для контроллера подробного представления, который отображает или редактирует отдельный управляемый объект, представление URI управляемого объекта будет добавлено в архив восстановления состояния:

- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
    NSManagedObjectID   *objectID   = [[self managedObject] objectID];

    [coder encodeObject:[objectID URIRepresentation] forKey:kManagedObjectKeyPath];
    [super encodeRestorableStateWithCoder:coder];
}

Когда состояние восстанавливается, вызывается метод UIStateRestoring -decodeRestorableStateWithCoder: для восстановления объекта из архивированной информации:

  1. Расшифруйте URI из архива восстановления.
  2. Получить идентификатор управляемого объекта для URI от координатора постоянного хранилища
  3. Получить экземпляр управляемого объекта из контекста управляемого объекта для этого идентификатора управляемого объекта

Например:

- (void) decodeRestorableStateWithCoder:(NSCoder *)coder {
    NSURL               *objectURI  = nil;
    NSManagedObjectID   *objectID   = nil;
    NSPersistentStoreCoordinator    *coordinator    = [[self managedObjectContext] persistentStoreCoordinator];

    objectURI = [coder decodeObjectForKey:kManagedObjectKeyPath];
    objectID = [coordinator managedObjectIDForURIRepresentation:objectURI];
    [[self managedObjectContext] performBlock:^{
        NSManagedObject *object = [self managedObjectContext] objectWithID:objectID];
        [NSOperationQueue mainQueue] addOperationWithBlock:^{
            [self setManagedObject:object];
        }];
    }]; 
}

И здесь все становится сложнее. В точке жизненного цикла приложения, где -decodeRestorableStateWithCoder: контроллеру представления потребуется правильный NSManagedObjectContext.

Передайте Батон против Государственного Восстановления: БОРЬБА!

При использовании подхода "передать эстафету" контроллер представления был создан в результате взаимодействия с пользователем, и был передан контекст управляемого объекта. Этот контекст управляемого объекта был связан с родительским контекстом или постоянным координатором хранилища.

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

К сожалению, запрашиваемая нами информация NSManagedObjectContext не может быть сохранена как часть архива восстановления. NSManagedObjectContext соответствует NSCoding, однако важные части этого не делают. NSPersistentStoreCoordinator нет, поэтому он не будет сохранен. Любопытно, что свойство parentContext для NSManagedObjectContext также не будет (я настоятельно рекомендовал бы подать выражение об этом). Хранение URL-адресов определенных экземпляров NSPersistentStore и воссоздание NSPersistentStoreCoordinator в каждом контроллере представления может показаться привлекательным вариантом, но в результате будет различный координатор для каждого контроллера представления - что может быстро привести к аварии.

Таким образом, хотя восстановление состояния может предоставить информацию, необходимую для обнаружения объектов в NSManagedObjectContext, оно не может напрямую предоставить то, что необходимо для воссоздания самого контекста.

Так, что дальше?

В конечном счете, то, что необходимо в контроллере представления -decodeRestorableStateWithCoder: это экземпляр NSManagedObjectContext который имеет то же происхождение, что и при кодировании состояния. Он должен иметь одинаковую структуру контекстов предков и постоянных хранилищ.

Восстановление состояния начинается в UIApplicationDelegate, где в качестве части процесса восстановления вызывается несколько методов делегата (-application:willFinishLaunchingWithOptions: -application:shouldRestoreApplicationState: -didDecodeRestorableStateWithCoder: -application:viewControllerWithRestorationIdentifierPath:coder:. Каждый из них - это возможность настроить процесс восстановления с самого начала и передать информацию, такую как присоединение экземпляра NSManagedObjectContext в качестве ссылки на связанный объект для NSCoder используемого для восстановления.

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

@implementation UIViewController (CoreData)

- (void) setManagedObjectContext:(NSManagedObjectContext *)context {
    [[self childViewControllers] makeObjectsPerformSelector:_cmd withObject:context];
}

@end

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

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

Реализация неформального протокола по умолчанию пошла бы дальше по цепочке респондента:

@implementation UIResponder (CoreData)

- (NSManagedObjectContext *) managedObjectContext {
    NSManagedObjectContext    *result = nil;

    if ([self nextResponder] != nil){
        if ([[self nextResponder] respondsToSelector:@selector(managedObjectContext)]){
            result = [[self nextResponder] managedObjectContext];
        }
    }
    return result;
}

@end

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

У вас также есть возможность использовать фабрики классов восстановления с восстановлением состояния для восстановления цепочки контекстов управляемого объекта во время восстановления.

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

Ответ 2

Я думаю, что лучшим способом справиться с этим было бы кодирование MOC в:

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder

а затем декодировать, когда он восстанавливается с помощью:

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder

Это должно сохранить проход подходов между восстановлением состояния.

Помните, что каждый VC, который использует MOC, должен реализовать это, если вы собираетесь с этим подходом.

Чтобы немного расшириться, используйте метод + (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder для инициализации вашего VC, тогда MOC должен быть декодирован с помощью упомянутого выше метода, и вы должны быть настроены.

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

Ответ 3

Я не сделал тонну с восстановлением состояния, но я бы подумал по этим строкам:

  • Вызывается ли первый участник приложения? Есть ли возможность для делегата приложения просматривать контроллеры представления?

  • Может ли контроллер просмотра приостанавливаться, пока он ожидает, что AppDelegate предоставит ему контекст?

Похоже, что State Restoration может быть особым случаем, но я бы рассмотрел возможность того, чтобы контроллеры представлений были достаточно умны, чтобы дождаться появления MOC перед запросом данных. Возможно даже наличие состояния отката в контроллерах представления, где они возвращаются в место, где контроллер просмотра может ждать контекста.

Ответ 4

Подкласс NSPersistentContainer (как состояние документов); принять протокол UIStateRestoring, чтобы он мог быть зарегистрирован с восстановлением состояния, что позволяет указателю быть доступным во время методов восстановления, сам объект фактически не закодирован в архиве. Позвоните в UIApplication registerObjectForStateRestoration внутри ленивого добытчика persistentContainer. Также в качестве состояния документов передайте persistentContainer контроллерам представлений в application:willFinishLaunching и prepareForSegue, а не только в контекст. Для контроллеров представления, которые не могут передать контейнер в willFinishLaunching, закодируйте persistentContainer и URI объекта в VC encodeRestorableStateWithCoder. Для демонстрационных (т.е. Push) VC, которые помещаются в главный стек навигации, используйте метод restorationClass и метод класса UIViewController viewControllerWithRestorationIdentifierPath и декодируйте persistentContainer, а затем с помощью existingObjectWithID возвращайте ноль, если объект больше не существует, что предотвращает создание VC, если он существует, тогда инициируйте VC, используя закодированную раскадровку и объект. В случае демонстрационных VC, которые всегда создаются независимо от существующего объекта, нет необходимости кодировать persistentContainer и не нужно проектировать класс восстановления, просто реализуйте делегат приложения application:viewControllerWithRestorationIdentifierPath: и используйте делегат приложения persistentContainer и установите объект на контроллере подробного представления из исходной раскадровки (захватите его в свойстве в application:willFinishedLaunching и очистите его при завершении восстановления, причина в том, что разделение может разрушиться до вызова application:viewControllerWithRestorationIdentifierPath:, то есть не может быть извлечены через окно).

Причина, по которой мы не декодируем объект в методе decodeRestorableStateWithCoder, заключается в том, что он вызывается после viewDidLoad, что слишком поздно, чтобы установить то, что нам нужно.

Ответ 5

Я узнал один очень чистый способ настроить стек Core Data из NSScreencast. В принципе, вы запускаете проект Xcode без выбора опции "Использовать основные данные". Затем вы добавляете одноэлементный класс, который является вашей моделью данных. Итак, чтобы получить главный MOC, вы бы сделали [[DataModel sharedModel] mainContext]. Я нахожу, что намного чище, чем все, что депонирует в App Delegate.

Я никогда не использовал его таким образом, но, по-моему, в вашем случае вы также можете сделать это в своих контроллерах вида:

-(NSManagedObjectContext*)moc
{ 
    if (_moc != nil) return _moc;
    _moc = [[DataModel sharedModel] mainContext];
    return _moc;
}

Ответ 6

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

Этот подход подтверждает, что Apple "передал эстафету" -подход, а также был удобным и совместимым с восстановлением состояния.