当应用程序进入后台时,是否期望NSTimer会触发?

18

我完全不理解,但是我的应用程序中的NSTimer 明显正在后台运行。我在计时器方法中放置了一个NSLog ,而且它在后台记录。这是使用iOS 4.2.1的iPhone 4。我已在Info.plist中声明了位置后台支持。

我阅读了文档和许多论坛上的讨论,它不应该是可能的。这是iOS的错误吗?还是未经记录的功能?我不想使用它,并在不久的将来发现,例如随着iOS 4.3的推出Apple默默地“修复”了它,导致应用程序无法正常工作。

有人知道更多关于它的信息吗?

2个回答

33

NSTimer会在主运行循环运行时触发。据我所知,苹果没有承诺取消预定计时器或阻止主运行循环的运行。当您转到后台时,您有责任取消预定计时器和释放资源。苹果不会为您做这件事情。但是如果您在不应该运行或使用太多秒钟的情况下运行,他们可能会杀死您。

系统中存在许多漏洞,可以允许应用在未获得授权的情况下运行。操作系统要防止这种情况将非常昂贵。但您不能依赖它。


好的,对我来说这已经足够了。谢谢。我不知道是否错过了什么。我肯定不会使用它。 - JakubM
我正在观察你所说的行为。我使用一个计时器来每10分钟刷新一次令牌。如果应用程序在后台运行了15分钟,然后回到前台,那么在回到前台时,计时器将会触发。我没有预料到这个情况。我认为计时器在后台不再运行,什么也不会发生。似乎所有未触发/未失效的计时器都被RunLoop强烈存储,并且在前台出现时,如果fireDate已经过去,则它们会立即被触发。所以,我可以安全地依赖于fireDate是否被传递,还是应该存储日期并进行比较? - mfaani

8

当您处于后台执行模式时,您可以设置一个定时器。以下是一些技巧:

  • 您需要使用beginBackgroundTaskWithExpirationHandler选择后台执行。
  • 如果您在后台线程上创建NSTimer,则需要手动将其添加到mainRunLoop中。

- (void)viewDidLoad
{
    // Avoid a retain cycle
    __weak ViewController * weakSelf = self;

    // Declare the start of a background task
    // If you do not do this then the mainRunLoop will stop
    // firing when the application enters the background
    self.backgroundTaskIdentifier =
    [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{

        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundIdentifier];
    }];

    // Make sure you end the background task when you no longer need background execution:
    // [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];


    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // Since we are not on the main run loop this will NOT work:
        [NSTimer scheduledTimerWithTimeInterval:0.5
                                         target:self
                                       selector:@selector(timerDidFire:)
                                       userInfo:nil
                                        repeats:YES];

        // This is because the |scheduledTimerWithTimeInterval| uses
        // [NSRunLoop currentRunLoop] which will return a new background run loop
        // which will not be currently running.
        // Instead do this:
        NSTimer * timer =
        [NSTimer timerWithTimeInterval:0.5
                                target:weakSelf
                              selector:@selector(timerDidFire:)
                              userInfo:nil
                               repeats:YES];

        [[NSRunLoop mainRunLoop] addTimer:timer
                                  forMode:NSDefaultRunLoopMode];
        // or use |NSRunLoopCommonModes| if you want the timer to fire while scrolling
    });
}

- (void) timerDidFire:(NSTimer *)timer
{
    // This method might be called when the application is in the background.
    // Ensure you do not do anything that will trigger the GPU (e.g. animations)
    // See: http://developer.apple.com/library/ios/DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW47
    NSLog(@"Timer did fire");
}

注意事项

  • 应用程序只能在后台执行约10分钟 - 超过这个时间,计时器将停止触发。
  • iOS 7及以上版本中,当设备被锁定时,前台应用程序几乎会立即被挂起。iOS 7应用程序锁定后,计时器将不会触发。

优秀的代码!这将有助于我完成我的应用程序,谢谢 :). 我有一个小问题,你所说的“当应用程序处于后台时可能会调用此方法”,是什么意思?我想使用它来每 3 小时从服务器更新数据。是否有更好的方法? - MQoder
1
当应用程序被放入后台时,它最多只能运行10分钟。从iOS 7开始,这10分钟可以是不连续的。您可以尝试使用iOS 7的新后台模式:大型上传/下载的后台传输或静默推送以远程唤醒您的应用程序。 - Robert
1
这段代码示例表明了对应用程序本身的前/后台状态和主队列与全局队列问题的混淆。没有必要将定时器的调度派发到一个全局队列上。只需像平常一样在主队列上安排定时器,并且 beginBackgroundTaskWithExpirationHandler 将保持应用程序的运行达数分钟之久。 - Rob
@Rob 我将其设定为 180 秒后才触发,而没有必要将其包装在 beginBackgroundTaskWithExpirationHandler 中...您为什么建议使用 beginBackgroundTaskWithExpirationHandler - mfaani
当你运行附加到Xcode调试器的应用程序时,你可能会这样做,这会改变应用程序的生命周期。如果不附加到Xcode上运行(例如退出Xcode并直接从设备上运行),你会发现应用程序会更快地被挂起(除非你进行了后台任务处理)。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接