【问题标题】:Scheduled NSTimer when app is in background?应用程序在后台时调度 NSTimer?
【发布时间】:2012-01-14 23:55:45
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 你有没有想过这个问题?我正在尝试做同样的事情,即允许计时器在应用程序处于后台时触发时运行代码。

标签: iphone ios nstimer uiapplicationdelegate


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    对我来说,后台任务和运行循环在大多数情况下都很关键且不准确。

    我决定使用 UserDefault 方法。

    第一步:添加应用进入后台/前台观察者

    第 2 步:当用户进入后台时,将计时器的时间与当前时间戳一起存储在用户默认值中

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

    全部完成。

    代码sn-p:

    // 添加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
    }
    

    【讨论】:

      【解决方案3】:

      创建全局 uibackground 任务标识符。

      UIBackgroundTaskIdentifier bgRideTimerTask;

      现在创建您的计时器并添加 BGTaskIdentifier 使用它,不要忘记在创建新的 Timer 对象时删除旧的 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];
      

      即使应用程序进入后台,这也对我有用。如果您发现新问题,请询问我。

      【讨论】:

        【解决方案4】:

        您需要在 Run loop 中添加计时器(Reference - Apple 开发者页面以了解 Run loop)。

        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中添加后台工作权限(必需的后台模式)。

        【讨论】:

          【解决方案5】:

          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 应用后,计时器不会触发。

          【讨论】:

          • @S_O 是的 - 我把它从一个工作测试项目中拉出来了。你有什么问题吗?
          • 是的,我有相同的代码,但是当我的应用程序在后台时,我的计时器根本没有触发。
          • @S_O 您在什么设备和 iOS 版本上进行了测试?你是怎么测试的?您应该点击主页按钮并观察控制台中的“计时器已触发”消息。
          • @Robert 谢谢,它帮我学习了很多教程,效果很好。
          • @Rob - 好地方。我会添加这个。
          【解决方案6】:

          如果您或其他人正在寻找如何在 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()
          }
          

          干杯!

          【讨论】:

          • 这段代码将在 180 秒 3 分钟后结束后台任务
          • 这段代码应该做什么?对我来说,这似乎是一个无操作?
          • 但是计时器安排在哪里?
          【解决方案7】:

          在 iOS 8 上,您可以通过一种简单的方式让您的 NSTimer 在应用处于后台(即使 iPhone 已锁定)长达约 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 分钟后计时器将不再触发,当应用返回前台时,它会再次开始触发

          【讨论】:

          • 完美答案。它就像魅力一样。适用于设备和模拟器。谢谢@Enrico Cupellini
          【解决方案8】:
          [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
              loop = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(Update) userInfo:nil repeats:YES];
              [[NSRunLoop currentRunLoop] addTimer:loop forMode:NSRunLoopCommonModes];
          

          【讨论】:

          • 不需要addTimer 调用。当您调用scheduledTimerWithTimeInterval 时,它已经在当前运行循环上安排了计时器。如果你在后台线程上安排这个,你会调用addTimer,但是,在这种情况下,你不会使用scheduledTimerWithTimeInterval,而是timerWithTimeInterval或类似的。但是这个问题不是关于从后台线程调度一个定时器,而是如何从主线程调度一个定时器,而是在应用程序本身离开前台时保持该定时器继续运行(这是一个完全 不同的问题)。
          【解决方案9】:

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

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

          【讨论】:

          • 你能解释一下吗?
          • 试过这个并没有什么区别,当应用程序激活时计时器总是启动。
          • 对我来说这是一个非常古老的帖子,你们当时找到解决方案了吗?
          【解决方案10】:

          你不应该通过设置定时器来解决这个问题,因为你不能在后台执行任何代码。想象一下,如果用户在此期间重新启动他的 iPhone 或在其他一些极端情况下会发生什么。

          使用 AppDelegate 的 applicationDidEnterBackground:applicationWillEnterForeground: 方法来获得您想要的行为。它更加健壮,因为当您的应用因重新启动或内存压力而完全终止时,它也可以工作。

          您可以保存应用程序进入后台时计时器接下来将触发的时间,并检查当应用程序返回前台时您是否应该采取行动。同样在此方法中停止和启动计时器。在您的应用运行时,您可以使用计时器在适当的时候触发更新。

          【讨论】:

          • 应用在后台时有没有办法让定时器触发?
          • 不,没有,在你的应用被iOS挂起后,就没有办法执行代码了。定时器也将暂停并仅在应用再次唤醒时触发。
          • 有没有其他方法可以让应用在后台指定时间运行代码?
          【解决方案11】:

          在后台时,这个计时器显然不会触发

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

          您可以找到有关 iOS 多任务处理方法的优秀文档here

          【讨论】:

          • 链接断开 :((
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-10
          • 1970-01-01
          • 2012-09-01
          • 2011-07-08
          • 2023-03-12
          相关资源
          最近更新 更多