Сохранение состояния iOS-приложения, часть 2

Продолжаем тему сохранения и восстановления состояния iOS-приложений.

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

Итак. Что же происходит, когда приложение запускается и имеет данные о сохраненном состоянии.

  1. Загрузка стартового изображения. Ничего нового.
  2. Загрузка приложения. Вызов методов делегата приложения.
    - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
    - (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder;
    
  3. Создание контроллеров. Вызывается метод делегата приложения, либо методы классов восстановления (подробнее ниже).
    // app delegate
    - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder;
    
    // restoration class
    + (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder;
    
  4. Восстановление состояние контроллеров и представлений. Вызывается метод восстановления состояния.
    - (void)decodeRestorableStateWithCoder:(NSCoder *)coder;
    
  5. Завершение восстановления, запуск приложения. Вызываются методы делегата приложения.
    - (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder;
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
    

Что нового в процессе загрузки?
-application:willFinishLaunchingWithOptions: вызывается до начала процесса восстановления – в этот момент приложение может подготовить ресурсы, необходимые для корректного восстановления. Следует помнить, что время запуска приложения ограничивается системой, не стоит выполнять длительные операции в этих методах.

Теперь подробнее рассмотрим процесс создания контроллеров. Когда мы назначаем Restoration ID в Interface Builder (или в коде), мы поручаем процессу восстановления создать контроллер.

  1. Проверяется, задано ли у контроллера свойство restorationClass. Если задано, вызывается метод у класса, указанного в данном свойстве.
    + (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder;
    
  2. Если свойство не задано, проверяется, существует ли метод делегата приложения.
    - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder;
    
  3. Если этот метод не существует, контроллер создается базовым инициализатором.

Таким образом, если не задавать свойство restorationClass и не реализовывать метод делегата, контроллер будет создан с настройками “по-умолчанию”. Конечно, затем при восстановлении с помощью метода -decodeRestorableStateWithCoder: восстанавливаются значения необходимых свойств. Для большинства случаев такого решения будет достаточно.

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

Методы +viewControllerWithRestorationIdentifierPath:coder: и -application:viewControllerWithRestorationIdentifierPath:coder: могут возвращать nil, что будет означать, что контроллер не нужно восстанавливать.

Наиболее удобным способом реализации методов создания контроллера будет реализация метода в самом классе контроллера.

@interface MyViewController : UIViewController <UIViewControllerRestoration>

@end

@implementation MyViewController

+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
    BOOL shouldRestore;
    UIViewController *vc = nil;

    // check coder for data, decide should we restore controller or not
    if (shouldRestore)
    {
        vc = [[self alloc] init];
        // do specific initialization, coder contains saved state
    }
    return vc;
}

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
    // restore controller state from coder
}

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    // save controller state to coder
}

-(void)viewDidLoad
{
    [super viewDidLoad];
    self.restorationClass = [self class];
}

@end

Не стоит в методе +viewControllerWithRestorationIdentifierPath:coder: полностью восстанавливать состояние контроллера. В этот момент не загружены представления, контроллер еще только создан. В этом месте необходимо указывать только те параметры контроллера, которые необходимы на этапе инициализации (те, которые будут нужны уже во время вызова -viewDidLoad).

В процессе восстановления передаваемый в методы объект NSCoder содержит некоторые вспомогательные объекты. С помощью ключа UIStateRestorationViewControllerStoryboardKey вы можете получить доступ к объекту UIStoryboard (например, для того, чтобы создать контроллер с помощью метода -instantiateViewControllerWithIdentifier:).

Кроме того, в ключе UIApplicationStateRestorationBundleVersionKey сохраняется версия (в виде NSString) приложения на момент сохранения состояния. В случае, если приложение было остановлено с сохранением состояния, а затем долго не использовалось, версия могла обновиться, а вместе с ней и существенно измениться пользовательский интерфейс. В таком случае, есть смысл отказаться от восстановления состояния, поскольку эти данные будут не актуальны в новой версии (стоит проверить значение этого ключа в -application:shouldRestoreApplicationState: и вернуть NO, если версия старая).

Помимо этого есть еще один ключ – UIApplicationStateRestorationUserInterfaceIdiomKey. В нем хранится тип интерфейса (iPhone или iPad). Его также стоит проверять в методе -application:shouldRestoreApplicationState: и восстанавливать только, если он совпадает с текущим. Пользователь мог сохранить резервную копию данных устройства, а затем восстановить ее на другом своем устройстве (например, копия была сделана на iPhone, а восстановлена на iPad). Если ваше приложение универсальное – его интерфейс может существенно отличаться, а значит данные восстановления с другого типа устройства не подойдут.

Документация Apple подробно описывает процесс сохранения и восстановления состояния приложения.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s