Вызовы жизненного цикла UIViewController в сочетании с восстановлением состояния

Я пытаюсь реализовать восстановление состояния в приложении, которое использует iOS 6+ и раскадровки, но у меня возникают проблемы с поиском способа предотвратить повторяющиеся вызовы тяжелых методов.

Если я просто запускаю приложение, то мне нужно настроить интерфейс в viewDidLoad:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
}

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

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
    [super decodeRestorableStateWithCoder:coder];
    // restore properties and stuff
    // [...]
    [self setupUI];
}

Итак, что происходит сейчас, это то, что сначала setupUI метод вызывается из viewDidLoadи затем снова из decodeRestorableStateWithCoder:, Я не вижу метод, который я могу переопределить, который всегда вызывается последним.

Это нормальный порядок вызовов методов:

  • awakeFromNib
  • viewDidLoad
  • viewWillAppear
  • viewDidAppear

При использовании восстановления состояния это называется:

  • awakeFromNib
  • viewDidLoad
  • decodeRestorableStateWithCoder
  • viewWillAppear
  • viewDidAppear

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

Было бы намного удобнее, если decodeRestorableStateWithCoder назывался ДО viewDidLoad потому что тогда вы можете использовать восстановленные свойства. К сожалению, это не так, так что... как я могу предотвратить выполнение работы в viewDidLoad когда я знаю, что мне нужно сделать это снова и снова в decodeRestorableStateWithCoder сразу после?

7 ответов

Решение
@property (nonatomic) BOOL firstLoad;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.firstLoad = YES;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    if (self.firstLoad) {
        [self setupUI];
        self.firstLoad = NO;
    }
}

Спасибо @calvinBhai за предложение.

Если вы делаете восстановление состояния программно (т.е. не используете раскадровки), вы можете использовать + viewControllerWithRestorationIdentifierPath:coder:инициализируйте контроллер представления там и используйте все, что вам нужно от кодера, чтобы выполнить инициализацию pre-viewDidLoad.

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
    if ([[identifierComponents lastObject] isEqualToString:kViewControllerRestorationIdentifier]) {
        if ([coder containsValueForKey:kIDToRestore]) {
            // Can only restore if we have an ID, otherwise return nil.
            int savedId = [coder decodeIntegerForKey:kIDToRestore];
            ViewController *vc = [[ViewController alloc] init];
            [vc setThingId:savedId];
            return vc;
        }
    }

    return nil;
}

Я обнаружил, что попытка реализовать восстановление состояния показала плохие методы программирования в моем коде, такие как слишком большая упаковка в viewDidLoad, Таким образом, хотя это работает (если вы не используете раскадровки), другой вариант - это рефакторинг того, как вы настраиваете свои контроллеры представления. Вместо использования флага переместите фрагменты кода в их собственные методы и вызовите эти методы из обоих мест.

Как ни странно, последовательность декодирования даже отличается и точно:

 +viewControllerWithRestorationIdentifierPath:coder:
 awakeFromNib
 viewDidLoad
 decodeRestorableStateWithCoder:
 viewWillAppear
 viewDidAppear

и это полностью имеет смысл, как это.

Из книги "Программирование iOS 9: погружение в представления, контроллеры представления и фреймворки", страницы 386-387.

Известный порядок событий при восстановлении состояния выглядит следующим образом:

  1. application:shouldRestoreApplicationState:
  2. application:viewControllerWithRestorationIdentifierPath:coder:
  3. viewControllerWithRestorationIdentifierPath:coder:по порядку вниз по цепочке
  4. viewDidLoadпо порядку вниз по цепочке; возможно, чередуется с вышеизложенным
  5. decodeRestorableStateWithCoder:по порядку вниз по цепочке
  6. application:didDecodeRestorableStateWithCoder:
  7. applicationFinishedRestoringStateпо порядку вниз по цепочке

Вы все еще не знаете когда viewWillAppear: а также viewDidAppear: прибудет или viewDidAppear: прибудет на всех. Но в applicationFinishedRestoringState Вы можете надежно завершить настройку вашего контроллера представления и вашего интерфейса.

Да, было бы лучше, если -decodeRestorableStateWithCoder: были вызваны раньше -viewDidLoad, Вздох.

Я переместил мой код установки вида (который зависит от восстанавливаемого состояния) в -viewWillAppear: и использовал dispatch_once()вместо логической переменной:

private var setupOnce: dispatch_once_t = 0

override func viewWillAppear(animated: Bool) {
    dispatch_once(&setupOnce) {
        // UI setup code moved to here
    }

    :
}

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

Добавляя к ответу Берби,

Фактический поток:

 initWithCoder
 +viewControllerWithRestorationIdentifierPath:coder:
 awakeFromNib
 viewDidLoad
 decodeRestorableStateWithCoder:
 viewWillAppear
 viewDidAppear

Знать, что внутри initWithCoderнужно установить self.restorationClass = [self class]; Это тогда заставит viewControllerWithRestorationIdentifierPath:coder: быть названным.

Я заметил, что установка splitViewController.delegate в willFinishLaunchingWithOptions причины viewDidLoad быть вызванным еще раньше. Так что, если вы переместите это в оба didFinishLaunchingWithOptions тогда вы можете успешно настроить свой контроллер представления внутри - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder до вызова viewDidLoad. Это может быть полезно для вас, так как у вас будет доступ к AppDelegate такие объекты, как persistentContainer.viewContext вместо того, чтобы регистрировать этот объект при восстановлении, чтобы к нему можно было обращаться по ссылке в ViewController - (void)decodeRestorableStateWithCoder:(NSCoder *)coder,

Одна поправка к потоку MixedCase (который был очень полезен, спасибо), фактический поток вызовов немного отличается:

Это нормальный порядок вызовов методов:

awakeFromNib

viewDidLoad

viewWillAppear

viewDidAppear

При использовании восстановления состояния это называется:

viewControllerWithRestorationIdentifierPath (декодировать любые данные, необходимые для обычного запуска)

awakeFromNib

viewDidLoad

viewWillAppear

viewDidAppear

decodeRestorableStateWithCoder (декодирует данные о состоянии восстановления и устанавливает пользовательский интерфейс контроллера)

Другие вопросы по тегам