我试图在使用iOS 6+和故事板的应用程序中实现状态恢复,但我在找到防止重量级方法重复调用的方法时遇到了问题。
如果我只是启动应用,那么我需要在viewDidLoad
中设置用户界面:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
}
这在正常的非状态恢复世界中运行良好。现在我已经添加了状态恢复,在恢复了一些属性后,我需要用这些属性更新UI:
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
// restore properties and stuff
// [...]
[self setupUI];
}
所以现在发生的事情是首先从setupUI
调用viewDidLoad
方法,然后再从decodeRestorableStateWithCoder:
调用。我没有看到一种方法,我可以覆盖它总是被称为最后一种方法。
这是方法调用的正常顺序:
使用状态恢复时,这称为:
我无法将调用发送到setupUI
viewWillAppear
,因为每当您回到视图时,它也会被执行。
如果在decodeRestorableStateWithCoder
之前调用viewDidLoad
会更加方便,因为那时你可以使用恢复的属性。可悲的是,事实并非如此......当我知道我需要在viewDidLoad
之后重新做一遍时,我怎能阻止在decodeRestorableStateWithCoder
中完成工作?
答案 0 :(得分:6)
如果您以编程方式进行状态恢复(即不使用故事板),则可以使用+ 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
中打包过多。因此,虽然这有效(如果您不使用故事板),另一个选择是重构您如何设置视图控制器。而不是使用标志,将代码片段移动到他们自己的方法,并从两个地方调用这些方法。
答案 1 :(得分:5)
有趣的是,解码序列甚至完全不同:
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
这样完全有道理。
答案 2 :(得分:4)
@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的建议。
答案 3 :(得分:2)
从书中"编程iOS 9:深入视图,视图控制器和框架"第386-387页
状态恢复期间已知的事件顺序如下:
application:shouldRestoreApplicationState:
application:viewControllerWithRestorationIdentifierPath:coder:
viewControllerWithRestorationIdentifierPath:coder:
,按顺序排列viewDidLoad
,按顺序排列;可能与上述decodeRestorableStateWithCoder:
,按顺序排列application:didDecodeRestorableStateWithCoder:
applicationFinishedRestoringState
,按顺序排列您仍然不知道viewWillAppear:
和viewDidAppear:
何时到达,或者viewDidAppear:
是否会到达。但是在applicationFinishedRestoringState
中,您可以可靠地完成视图控制器和界面的配置。
答案 4 :(得分:0)
是的,如果在-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
在视图控制器的生命周期内应该是正确的。
答案 5 :(得分:0)
添加berbie的答案,
实际流程为:
initWithCoder
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
请注意,在initWithCoder
内,您需要设置self.restorationClass = [self class];
这将强制调用viewControllerWithRestorationIdentifierPath:coder:
。
答案 6 :(得分:0)
我注意到在splitViewController.delegate
中设置willFinishLaunchingWithOptions
导致viewDidLoad
被更早调用。因此,如果将其移至两个didFinishLaunchingWithOptions
,则可以在调用viewDidLoad之前在- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder
内成功配置视图控制器。无论如何,这样做可能对您很有用,因为您可以访问AppDelegate
之类的persistentContainer.viewContext
对象,而不需要在恢复时注册该对象,因此可以通过ViewController的引用对其进行访问。 - (void)decodeRestorableStateWithCoder:(NSCoder *)coder
。
答案 7 :(得分:-1)
对MixedCase流程的一次修正(非常有帮助,谢谢),实际的呼叫流程有点不同:
这是方法调用的正常顺序:
awakeFromNib
viewDidLoad中
viewWillAppear中
viewDidAppear
使用状态恢复时,这称为:
viewControllerWithRestorationIdentifierPath(解码常规启动所需的任何数据)
awakeFromNib
viewDidLoad中
viewWillAppear中
viewDidAppear
decodeRestorableStateWithCoder (解码可恢复状态数据,并设置控制器UI)