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

时间:2013-08-07 15:22:53

标签: ios uiviewcontroller state

我试图在使用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:调用。我没有看到一种方法,我可以覆盖它总是被称为最后一种方法。

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

  • awakeFromNib
  • viewDidLoad中
  • viewWillAppear中
  • viewDidAppear

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

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

我无法将调用发送到setupUI viewWillAppear,因为每当您回到视图时,它也会被执行。

如果在decodeRestorableStateWithCoder之前调用viewDidLoad会更加方便,因为那时你可以使用恢复的属性。可悲的是,事实并非如此......当我知道我需要在viewDidLoad之后重新做一遍时,我怎能阻止在decodeRestorableStateWithCoder中完成工作?

8 个答案:

答案 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页

状态恢复期间已知的事件顺序如下:

  1. application:shouldRestoreApplicationState:
  2. application:viewControllerWithRestorationIdentifierPath:coder:
  3. viewControllerWithRestorationIdentifierPath:coder:,按顺序排列
  4. viewDidLoad,按顺序排列;可能与上述
  5. 交织
  6. decodeRestorableStateWithCoder:,按顺序排列
  7. application:didDecodeRestorableStateWithCoder:
  8. applicationFinishedRestoringState,按顺序排列
  9. 您仍然不知道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)