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页
国家恢复期间事件的已知顺序是这样的:
-
application:shouldRestoreApplicationState:
-
application:viewControllerWithRestorationIdentifierPath:coder:
-
viewControllerWithRestorationIdentifierPath:coder:
顺序链下 -
viewDidLoad
,为了下链; 可能与前述交错 -
decodeRestorableStateWithCoder:
顺序链下 -
application:didDecodeRestorableStateWithCoder:
-
applicationFinishedRestoringState
,顺序链下
你仍然不知道什么时候viewWillAppear:
和viewDidAppear:
会到来,或者viewDidAppear:
是否到达。 但在applicationFinishedRestoringState
您可以可靠地完成对视图控制器和界面的configuration。
一个更正MixedCasestream(这是非常有益的,谢谢),实际的呼叫stream程有点不同:
这是方法调用的正常顺序:
awakeFromNib
viewDidLoad中
viewWillAppear中
viewDidAppear
当使用状态恢复时,这被称为:
viewControllerWithRestorationIdentifierPath(解码定期启动所需的任何数据)
awakeFromNib
viewDidLoad中
viewWillAppear中
viewDidAppear
decodeRestorableStateWithCoder (解码可恢复的状态数据,并设置你的控制器UI)