【问题标题】:Swift 3 - How to make timer work in backgroundSwift 3 - 如何让计时器在后台工作
【发布时间】:2017-02-18 18:39:23
【问题描述】:

我正在尝试做一个可以让计时器在后台运行的应用程序。

这是我的代码:

let taskManager = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.scheduleNotification), userInfo: nil, repeats: true)
        RunLoop.main.add(taskManager, forMode: RunLoopMode.commonModes)

上面的代码将执行一个调用本地通知的函数。 这在应用程序在前台时有效,我怎样才能让它在后台工作?

我尝试放置几行打印,我看到当我最小化(按下主页按钮)应用程序时,计时器停止,当我返回应用程序时,它恢复。

我希望计时器仍然在后台运行。有什么办法吗?

这就是我想要发生的事情:

运行应用程序 -> 等待 10 秒 -> 收到通知 -> 等待 10 秒 -> 收到通知 -> 然后返回等待并再次收到

在前台时会发生这种情况。但不在后台。请帮忙。

【问题讨论】:

    标签: ios swift timer


    【解决方案1】:

    这行得通。正如另一个答案中所建议的那样,它在异步任务中使用 while 循环,但它也包含在后台任务中

    func executeAfterDelay(delay: TimeInterval, completion: @escaping(()->Void)){
        backgroundTaskId = UIApplication.shared.beginBackgroundTask(
            withName: "BackgroundSound",
            expirationHandler: {[weak self] in
                if let taskId = self?.backgroundTaskId{
                    UIApplication.shared.endBackgroundTask(taskId)
                }
            })
        
        let startTime = Date()
        DispatchQueue.global(qos: .background).async {
            while Date().timeIntervalSince(startTime) < delay{
                Thread.sleep(forTimeInterval: 0.01)
            }
            DispatchQueue.main.async {[weak self] in
                completion()
                if let taskId = self?.backgroundTaskId{
                    UIApplication.shared.endBackgroundTask(taskId)
                }
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      您可以转到功能并打开后台模式和活动音频。 AirPlay,以及图片和图片。

      确实有效。你不需要设置 DispatchQueue 。 你可以使用定时器。

      Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (t) in
      
         print("time")
      }
      

      【讨论】:

      • 感谢您的回答。您是否必须播放声音才能使其正常工作?还是在设置音频背景模式后才起作用?
      • 没有。我不需要播放声音。我有一个用于验证码的计时器。当我的应用程序进入后台时,我的计时器停止了。
      • 小心其中一些,众所周知,苹果会拒绝滥用功能用于上述目的之外的应用程序。如果您将应用设置为音频背景,则会影响更广泛系统的音量设置等行为。
      • 非常棒的解决方案。我想问一个问题,在计时器用完后,当应用程序在后台时,我们如何打开特定的视图控制器?当计时器在前台用完时,我可以执行segue,我们如何在后台执行?谁能帮我解决这个问题?
      • 这根本不能回答问题。 Cristina 询问如何让它在后台工作,而您的代码没有这样做。它只启动计时器并在前台触发它。
      【解决方案3】:

      斯威夫特 4、斯威夫特 5

      我更喜欢不在后台任务上运行计时器,只需比较 applicationDidEnterBackground 和 applicationWillEnterForeground 之间的日期秒数。

      func setup() {
          NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
          NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
      }
      
      @objc func applicationDidEnterBackground(_ notification: NotificationCenter) {
          appDidEnterBackgroundDate = Date()
      }
      
      @objc func applicationWillEnterForeground(_ notification: NotificationCenter) {
          guard let previousDate = appDidEnterBackgroundDate else { return }
          let calendar = Calendar.current
          let difference = calendar.dateComponents([.second], from: previousDate, to: Date())
          let seconds = difference.second!
          countTimer -= seconds
      }
      

      【讨论】:

        【解决方案4】:

        如果 1 或 2 秒的阈值是可以接受的,这个 hack 可能会有所帮助。

        UIApplication.didEnterBackgroundNotification
        UIApplication.willEnterForegroundNotification
        

        didEnterBackground 上停止计时器和备份Date()。 将Date() 添加到willEnterForegraound 的备份日期以获得总时间。 启动计时器并将总日期添加到计时器。

        注意:如果用户更改了系统的日期时间,它将被破坏!

        【讨论】:

          【解决方案5】:

          您可以通过获取应用程序的后台和前台状态之间的延时来实现这一点,这里是代码 sn-p。

          导入基础 导入 UIKit

          自定义定时器类 {

          let timeInterval: TimeInterval
          var backgroundTime : Date?
          var background_forground_timelaps : Int?
          
          
          init(timeInterval: TimeInterval) {
              self.timeInterval = timeInterval
          }
          
          private lazy var timer: DispatchSourceTimer = {
              let t = DispatchSource.makeTimerSource()
              t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
              t.setEventHandler(handler: { [weak self] in
                  self?.eventHandler?()
              })
              return t
          }()
          
          var eventHandler: (() -> Void)?
          
          private enum State {
              case suspended
              case resumed
          }
          
          private var state: State = .suspended
          
          deinit {
              NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
              NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
              timer.setEventHandler {}
              timer.cancel()
              resume()
              eventHandler = nil
          }
          
          func resume() {
              
              NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
              
              NotificationCenter.default.addObserver(self, selector: #selector(willEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
              
              if state == .resumed {
                  return
              }
              state = .resumed
              timer.resume()
          }
          
          func suspend() {
              if state == .suspended {
                  return
              }
              state = .suspended
              timer.suspend()
          }
          
          
          @objc fileprivate func didEnterBackgroundNotification() {
              self.background_forground_timelaps = nil
              self.backgroundTime = Date()
          }
          
          @objc fileprivate func willEnterForegroundNotification() {
              // refresh the label here
              self.background_forground_timelaps = Date().interval(ofComponent: .second, fromDate: self.backgroundTime ?? Date())
              self.backgroundTime = nil
          }
          

          }

          像这样使用这个类;

          self.timer = CustomTimer(timeInterval: 1)
                  self.timer?.eventHandler = {
                      DispatchQueue.main.sync {
                         
                          var break_seconds = self.data.total_break_sec ?? 0
                              break_seconds += 1
                              if self.timer?.background_forground_timelaps != nil && self.timer?.backgroundTime == nil{
                                  break_seconds += (self.timer?.background_forground_timelaps)!
                                  self.timer?.background_forground_timelaps = nil
                              }
                              self.data.total_break_sec = String(break_seconds)
                              self.lblBreakTime.text = PRNHelper.shared.getPlainTimeString(time: TimeInterval(break_seconds))
                          
                      }
                  }
                  self.timer?.resume()
          

          这样,当从后台恢复应用程序时,我可以正确设置计时器。

          【讨论】:

            【解决方案6】:

            计时器无法在后台工作。对于后台任务,您可以查看下面的链接...

            https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started

            【讨论】:

              【解决方案7】:

              只有满足以下两个条件时,计时器才能在后台运行:

              • 由于某些其他原因,您的应用程序在后台运行。 (大多数应用程序不会;大多数应用程序在进入后台时会暂停。)并且:

              • 当应用程序进入进入后台时,计时器已经在运行。

              【讨论】:

              • 您不能通过将计时器包装在 backgroundTask 中来开始在后台运行计时器吗?虽然那样它最终会被它的 expireHandler 杀死(3分钟)?
              • 根据我的经验,如果应用程序没有启动任何后台模式,NSTimer 将在应用程序进入后台时停止运行。但是当应用回到前台时,定时器可以继续运行,但是会丢失在后台的时间间隔。
              • 那么iphone定时器是如何准确工作的呢?......
              • @Martian2049 这是苹果公司的。苹果可以做你做不到的事情。
              • 我想通了。他们计算时间流逝
              【解决方案8】:

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

              创建全局 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];
              

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

              【讨论】:

              • Objective-C aint Swift
              • 快速版本?即使转换为快速代码似乎也无法正常工作......有些东西坏了。
              • 这是如何工作的,我需要在哪里添加此代码?你能解释一下吗
              【解决方案9】:

              正如其他人指出的那样,Timer 不能让方法在后台运行。你可以做的是在异步任务中使用while循环

              DispatchQueue.global(qos: .background).async {
                              while (shouldCallMethod) {
                                  self.callMethod()
                                  sleep(1)
                              }
              }
              

              【讨论】:

              • 很好的解决方案!
              • 是否像大多数后台任务一样限制为 10 分钟?
              • 这仅在应用程序处于前台或锁定将应用程序保持在前台时有效,但如果我单击主页按钮并将应用程序置于后台则不起作用。请建议
              【解决方案10】:

              你真的不需要跟上一个 NSTImer 对象。每个位置更新都有自己的时间戳。

              因此,您可以跟上上次与当前时间的对比,并且在达到该阈值后每隔一段时间执行一项任务:

                          if let location = locations.last {
                              let time = location.timestamp
              
                              guard let beginningTime = startTime else {
                                  startTime = time // Saving time of first location time, so we could use it to compare later with subsequent location times.
                                  return //nothing to update
                              }
              
                              let elapsed = time.timeIntervalSince(beginningTime) // Calculating time interval between first and second (previously saved) location timestamps.
              
                              if elapsed >= 5.0 { //If time interval is more than 5 seconds
                                  //do something here, make an API call, whatever.
              
                                  startTime = time
                              }
              }
              

              【讨论】:

                猜你喜欢
                • 2022-12-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-11-25
                • 1970-01-01
                • 1970-01-01
                • 2018-07-06
                • 1970-01-01
                相关资源
                最近更新 更多