Вызовы жизненного цикла 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.
Известный порядок событий при восстановлении состояния выглядит следующим образом:
application:shouldRestoreApplicationState:
application:viewControllerWithRestorationIdentifierPath:coder:
viewControllerWithRestorationIdentifierPath:coder:
по порядку вниз по цепочкеviewDidLoad
по порядку вниз по цепочке; возможно, чередуется с вышеизложеннымdecodeRestorableStateWithCoder:
по порядку вниз по цепочкеapplication:didDecodeRestorableStateWithCoder:
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 (декодирует данные о состоянии восстановления и устанавливает пользовательский интерфейс контроллера)