应用程序在后台时预定的NSTimer?

时间:2011-12-07 13:13:31

标签: iphone ios nstimer uiapplicationdelegate

当应用在后台时,人们如何处理预定的NSTimer?

假设我每小时都在我的应用中更新一些内容。

updateTimer = [NSTimer scheduledTimerWithTimeInterval:60.0*60.0 
target:self 
selector:@selector(updateStuff) 
userInfo:nil 
repeats:YES];

在后台时,此计时器显然不会触发(?)。当用户回到应用程序时应该怎么办?计时器是否仍在运行,时间相同?

如果用户在一小时内回来,会发生什么。它会在错过的所有时间触发,还是等到下一个更新时间?

我希望它做的是在应用程序进入前台后立即更新,如果它应该触发的日期是过去的。这可能吗?

12 个答案:

答案 0 :(得分:42)

can在后台执行模式下有计时器触发。有几个技巧:

如果你在主线上:

{
    // 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.backgroundTaskIdentifier];
    }];

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

    [NSTimer scheduledTimerWithTimeInterval:0.5
                                     target:self
                                   selector:@selector(timerDidFire:)
                                   userInfo:nil
                                    repeats:YES];
}

- (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
}

备注

  • 应用程序仅执行约10分钟(从iOS 7开始约3分钟)后台执行 - 此后计时器将停止触发。
  • 从iOS 7开始,当设备锁定时,它几乎会立即暂停前台应用。锁定iOS 7应用程序后,计时器不会启动。

答案 1 :(得分:38)

您不应该通过设置计时器来解决此问题,因为您不允许在后台执行任何代码。想象一下,如果用户在此期间或其他边缘情况下重启iPhone,将会发生什么。

使用AppDelegate的applicationDidEnterBackground:applicationWillEnterForeground:方法获取所需的行为。它的功能更强大,因为当你的应用程序因重启或内存压力而被完全杀死时,它也会起作用。

当应用程序进入后台时,您可以节省计时器下次触发的时间,并检查应用程序返回前台时是否应该采取措施。也可以在此方法中停止并启动计时器。在您的应用程序运行时,您可以使用计时器在适当的时刻触发更新。

答案 2 :(得分:17)

如果您或其他人正在寻找如何在Swift的后台运行NSTimer,请将以下内容添加到您的App Delegate:

var backgroundUpdateTask: UIBackgroundTaskIdentifier = 0


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    return true
}

func applicationWillResignActive(application: UIApplication) {
    self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
        self.endBackgroundUpdateTask()
    })
}

func endBackgroundUpdateTask() {
    UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
    self.backgroundUpdateTask = UIBackgroundTaskInvalid
}

func applicationWillEnterForeground(application: UIApplication) {
    self.endBackgroundUpdateTask()
}

干杯!

答案 3 :(得分:6)

在iOS 8上,当应用程序处于后台时(即使iphone被锁定),你可以通过一种简单的方式让你的NSTimer正常工作~3分钟:

在您需要的地方照常实施scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:方法。在AppDelegate内部创建一个属性:

UIBackgroundTaskIdentifier backgroundUpdateTask

然后在你的appDelegate方法中:

- (void)applicationWillResignActive:(UIApplication *)application {
    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
    [self endBackgroundUpdateTask];
    }];
}

- (void) endBackgroundUpdateTask
{
    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
    self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    [self endBackgroundUpdateTask];
}

3分钟后计时器不会点火更多,当应用程序在前台重新开始时它将再次开火

答案 4 :(得分:5)

  

当在后台时,此计时器显然不会触发

This post表明事情并不那么清楚。当应用程序进入后台时,您应该使计时器无效,并在它返回前台时重新启动它们。在后台运行东西可能是可能的,但是再次它可能会让你被杀......

您可以找到有关iOS多任务处理方法here的良好文档。

答案 5 :(得分:3)

您需要在当前运行循环中添加计时器。

[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSRunLoopCommonModes];

答案 6 :(得分:2)

[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    loop = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(Update) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:loop forMode:NSRunLoopCommonModes];

答案 7 :(得分:1)

您需要在运行循环中添加计时器(Reference - 用于运行循环理解的Apple开发人员页面)。

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:@selector(updateTimer)  userInfo:nil  repeats:true];

[[NSRunLoop mainRunLoop] addTimer: timer forMode:NSRunLoopCommonModes];

//funcation

-(void) updateTimer{
NSLog(@"Timer update");

}

您需要在 infoPlist 中添加后台工作的权限(必需的后台模式)。

答案 8 :(得分:0)

创建全局uibackground任务标识符。

{1, 1.00001, 2, 3}

现在创建您的计时器并添加BGTaskIdentifier,不要忘记在创建新的计时器对象时删除旧的BGTaskIdentifier。

UIBackgroundTaskIdentifier bgRideTimerTask;

即使应用程序在后台运行,这里对我仍然有效。如果发现新问题,请问我。

答案 9 :(得分:0)

==============对于目标c ===============

创建全局uibackground任务标识符。

UIBackgroundTaskIdentifier bgRideTimerTask;

现在创建您的计时器并添加BGTaskIdentifier,不要忘记在创建新的计时器对象时删除旧的BGTaskIdentifier。

 [timerForRideTime invalidate];
 timerForRideTime = nil;

 bgRideTimerTask = UIBackgroundTaskInvalid;

 UIApplication *sharedApp = [UIApplication sharedApplication];       
 bgRideTimerTask = [sharedApp beginBackgroundTaskWithExpirationHandler:^{

                            }];
 timerForRideTime =  [NSTimer scheduledTimerWithTimeInterval:1.0
                                                                                 target:self
                                                                               selector:@selector(timerTicked:)
                                                                               userInfo:nil
                                                                                repeats:YES]; 
                                                   [[NSRunLoop currentRunLoop]addTimer:timerForRideTime forMode: UITrackingRunLoopMode];

即使应用程序在后台运行,这里对我仍然有效。如果发现新问题,请问我。

答案 10 :(得分:0)

对我来说,后台任务和运行循环很关键,而且大多数时候都不准确。

我决定使用UserDefault方法。

步骤1:添加应用输入背景/前景观察者

第2步:当用户进入后台时,将计时器的时间存储在用户默认值中并带有当前时间戳记

第3步:当用户进入前台时,将用户的默认时间戳与当前时间戳进行比较,并计算出新的计时器

全部完成。

代码段:

//添加到viewDidLoad / init函数中

NotificationCenter.default.addObserver(self, selector: #selector(self.background(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.foreground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)

//添加到VC / View

let userDefault = UserDefaults.standard

@objc func background(_ notification: Notification) {
    userDefault.setValue(timeLeft, forKey: "OTPTimer")
    userDefault.setValue(Date().timeIntervalSince1970, forKey: "OTPTimeStamp")
}

@objc func foreground(_ notification: Notification) {
    let timerValue: TimeInterval = userDefault.value(forKey: "OTPTimer") as? TimeInterval ?? 0
    let otpTimeStamp = userDefault.value(forKey: "OTPTimeStamp") as? TimeInterval ?? 0
    let timeDiff = Date().timeIntervalSince1970 - otpTimeStamp
    let timeLeft = timerValue - timeDiff
    completion((timeLeft > 0) ? timeLeft : 0)
    print("timeLeft:", timeLeft) // <- This is what you need
}

答案 11 :(得分:0)

Swift 4+ 版本的 gellieb's answer

var backgroundUpdateTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)


func applicationWillResignActive(_ application: UIApplication) {
    self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
        self.endBackgroundUpdateTask()
    })
}
func endBackgroundUpdateTask() {
    UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
    self.backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
}

func applicationWillEnterForeground(application: UIApplication) {
    self.endBackgroundUpdateTask()
}