UIViewController生命周期调用结合状态恢复

我试图在使用iOS 6+和故事板的应用程序中实现状态恢复,但是我遇到了一些问题,以防止重复调用重复方法。

如果我只是启动应用程序,那么我需要在viewDidLoad设置UI:

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

这在一个正常的,非国家恢复的世界里工作得很好。 现在我已经添加了状态恢复和恢复一些属性后,我需要更新与这些属性的用户界面:

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

所以现在发生的是,首先从viewDidLoad调用setupUI方法,然后再从decodeRestorableStateWithCoder:调用setupUI方法。 我没有看到一个我可以覆盖的方法,总是被称为最后一个。

这是方法调用的正常顺序:

  • awakeFromNib
  • viewDidLoad中
  • viewWillAppear中
  • viewDidAppear

当使用状态恢复时,这被称为:

  • awakeFromNib
  • viewDidLoad中
  • decodeRestorableStateWithCoder
  • viewWillAppear中
  • viewDidAppear

我不能把调用的setupUI放在viewWillAppear因为这样它也会在你每次回到视图时执行。

如果decodeRestorableStateWithCoder被调用BEFORE的viewDidLoad将会更方便,因为那样你可以使用恢复的属性。 可悲的是,不是这样,所以…我怎么能阻止做viewDidLoad的工作,当我知道我需要在decodeRestorableStateWithCoder之后重新做一遍?

如果你正在编程状态恢复状态(即不使用故事板),你可以使用+ viewControllerWithRestorationIdentifierPath:coder:在那里初始化视图控制器,并使用编码器所需的任何东西来做预视图的初始化。

 + (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打包太多。 所以,虽然这工作(如果你不使用故事板),另一种select是重构如何设置您的视图控制器。 而不是使用一个标志,将代码块移动到他们自己的方法,并从这两个地方调用这些方法。

有趣的是,解码序列甚至是完全不同的:

  +viewControllerWithRestorationIdentifierPath:coder: awakeFromNib viewDidLoad decodeRestorableStateWithCoder: viewWillAppear viewDidAppear 

这完全是有道理的

 @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的build议。

是的,如果在-viewDidLoad之前调用了-decodeRestorableStateWithCoder:确实会更好。 叹。

我移动我的视图设置代码(这取决于可恢复状态),以-viewWillAppear:和使用dispatch_once() ,而不是一个布尔variables:

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

该文档指出“视图不再在低内存条件下清除”,所以dispatch_once在视图控制器的生存dispatch_once应该是正确的。

除了berbie的回答,

实际stream程是:

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

注意,在initWithCoder里面,你需要设置self.restorationClass = [self class]; 这将强制viewControllerWithRestorationIdentifierPath:coder:被调用。

从“编程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您可以可靠地完成对视图控制器和界面的configuration。

一个更正MixedCasestream(这是非常有益的,谢谢),实际的呼叫stream程有点不同:

这是方法调用的正常顺序:

awakeFromNib

viewDidLoad中

viewWillAppear中

viewDidAppear

当使用状态恢复时,这被称为:

viewControllerWithRestorationIdentifierPath(解码定期启动所需的任何数据)

awakeFromNib

viewDidLoad中

viewWillAppear中

viewDidAppear

decodeRestorableStateWithCoder (解码可恢复的状态数据,并设置你的控制器UI)