Swift 3 - 如何让计时器在后台运行

时间:2017-02-18 18:39:24

标签: ios swift timer

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

这是我的代码:

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秒 - >已收到通知 - >然后回来等待再次收到

在前景时发生。但不是在后台。请帮助。

9 个答案:

答案 0 :(得分:9)

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

它确实有效。你不需要设置DispatchQueue。 你可以使用Timer。

Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (t) in

   print("time")
}

答案 1 :(得分:6)

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

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

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

您真的不需要跟上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
                }
}

答案 4 :(得分:0)

如果可接受1或2秒的阈值,则此黑客可能会有所帮助。

UIApplication.didEnterBackgroundNotification
UIApplication.willEnterForegroundNotification

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

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

答案 5 :(得分:0)

您可以通过获取应用程序的背景和前景状态之间的时间间隔来实现此目的,这是代码段。

导入基金会 导入UIKit

CustomTimer类{

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 :(得分:0)

这有效。如另一个答案所示,它在异步任务内使用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)
            }
        }
    }
}

答案 7 :(得分:-1)

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

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

答案 8 :(得分:-3)

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

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