问题 application:didFinishLaunchingWithOptions:在创建目标控制器之前触发通知


你好, 我正在编写一个应用程序,当使用本地通知打开它时,它应该响应UI更新和内部状态更改。我正在使用故事板,我已经设置了我的主视图控制器来观察状态变化:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // ...
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeByNotification:) name:@"Resume" object:nil];
}

在我的app委托中,我有这个:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    if (application.applicationState == UIApplicationStateInactive)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Resume" object:self userInfo:notification.userInfo];
    }
}

这很好用:如果应用程序在后台运行,视图控制器将截取通知并做出相应的反应。 (如果应用程序在前台运行,则会被忽略,因为UI正在直接处理。)

当应用程序被杀死并收到通知时会出现问题。我在didFinishLaunchingWithOptions方法中写了这个,让手机振动成为一种快速的调试技术:),我  得到通知:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotification)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Resume" object:self userInfo:localNotification.userInfo];
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    }

    return YES;
}

电话会振动,因此通知就在那里,但它似乎没有触发观察者。我想这是因为尚未调用视图控制器的didViewLoad方法。我不知道如何解决这个问题。我想我可以使用UIStoryboard的instantiateViewControllerWithIdentifier:方法来确保视图控制器实际存在,但除了最终将由故事板自己的生命周期实例化之外,我不会得到它的“额外”实例。 ?从课堂参考文献中的内容来看,这并不意味着做这种事情。

我错过了一些非常明显的东西吗?事实上,对于这种情况,我的方法是正确的吗?

谢谢!


10437
2018-02-10 19:48


起源



答案:


视图控制器不会加载其视图,直到有人要求它查看它的视图。在发布时,通常发生在之后 application:didFinishLaunchingWithOptions: 回报。

你可能想知道为什么。答案是您可以在启动时实例化多个视图控制器,其中一些是隐藏的。例如,如果窗口的根视图控制器是a UINavigationController,您可以使用一堆视图控制器(用户上次运行应用程序时推送的堆栈)加载导航控制器。只有该堆栈的顶视图控制器可见,因此无需加载其他视图控制器的视图。系统等到 application:didFinishLaunchingWithOptions: 在加载任何视图之前返回,以便只加载必要的视图。

因此解决问题的一种方法就是向视图控制器询问其视图,从而强制它加载。如果您的视图控制器是窗口的根视图控制器,您可以这样做:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotification) {
        [[self.window rootViewController] view];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Resume" object:self userInfo:localNotification.userInfo];
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    }

    return YES;
}

一种不同的解决方法是开始在视图控制器中观察通知 initWithCoder: 方法:

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeByNotification:) name:@"Resume" object:nil];
    }
    return self;
}

当从MainStoryboard实例化视图控制器时调用此方法,该操作系统在之前发生 application:didFinishLaunchingWithOptions: 信息。


12
2018-02-10 20:03



谢谢,使用 initWithcoder: 像魅力一样工作。对象生命周期中的所有这些步骤都会让人感到困惑。 :) - Jollino
谢谢,如果您使用推送通知而不是本地通知而没有故事板,这也适用。只需使用 init 方法。 - yoeriboven
init(coder:) - rob mayoff


答案:


视图控制器不会加载其视图,直到有人要求它查看它的视图。在发布时,通常发生在之后 application:didFinishLaunchingWithOptions: 回报。

你可能想知道为什么。答案是您可以在启动时实例化多个视图控制器,其中一些是隐藏的。例如,如果窗口的根视图控制器是a UINavigationController,您可以使用一堆视图控制器(用户上次运行应用程序时推送的堆栈)加载导航控制器。只有该堆栈的顶视图控制器可见,因此无需加载其他视图控制器的视图。系统等到 application:didFinishLaunchingWithOptions: 在加载任何视图之前返回,以便只加载必要的视图。

因此解决问题的一种方法就是向视图控制器询问其视图,从而强制它加载。如果您的视图控制器是窗口的根视图控制器,您可以这样做:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotification) {
        [[self.window rootViewController] view];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Resume" object:self userInfo:localNotification.userInfo];
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    }

    return YES;
}

一种不同的解决方法是开始在视图控制器中观察通知 initWithCoder: 方法:

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeByNotification:) name:@"Resume" object:nil];
    }
    return self;
}

当从MainStoryboard实例化视图控制器时调用此方法,该操作系统在之前发生 application:didFinishLaunchingWithOptions: 信息。


12
2018-02-10 20:03



谢谢,使用 initWithcoder: 像魅力一样工作。对象生命周期中的所有这些步骤都会让人感到困惑。 :) - Jollino
谢谢,如果您使用推送通知而不是本地通知而没有故事板,这也适用。只需使用 init 方法。 - yoeriboven
init(coder:) - rob mayoff