快速 NSTimer 在后台 [英] swift NSTimer in Background

查看:20
本文介绍了快速 NSTimer 在后台的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了很多关于如何在堆栈或其他地方的后台处理 NSTimer 的问题.我已经尝试了所有真正有意义的选项之一..当应用程序进入后台时停止计时器

I have come across a lot of issues with how to handle NSTimer in background here on stack or somewhere else. I've tried one of all the options that actually made sense .. to stop the timer when the application goes to background with

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidBecomeActive", name: UIApplicationWillEnterForegroundNotification, object: nil)

一开始我以为我的问题解决了,我只是保存了应用进入后台的时间并计算了应用进入前台的时间差..但后来我发现时间实际上推迟了 3, 4 ,5 秒 .. 它实际上不一样 .. 我已经将它与另一台设备上的秒表进行了比较.

At first I thought that my problem is solved, I just saved the time when the app did enter background and calculated the difference when the app entered foreground .. but later I noticed that the time is actually postponed by 3, 4 , 5 seconds .. that it actually is not the same .. I've compared it to the stopwatch on another device.

在后台运行 NSTimer 真的有任何可靠的解决方案吗?

Is there REALLY any SOLID solution to running an NSTimer in background?

推荐答案

你不应该根据它进入后台或恢复的时间来进行任何调整,而只是节省你从或到的时间(取决于取决于你是向上还是向下数).然后,当应用程序再次启动时,您只需在重建计时器时使用该从/到时间.

You shouldn't be messing with any adjustments based upon when it enters background or resumes, but rather just save the time that you are counting from or to (depending upon whether you are counting up or down). Then when the app starts up again, you just use that from/to time when reconstructing the timer.

同样,请确保您的计时器处理程序不依赖于调用处理选择器的确切时间(例如,not 是否执行 seconds++ 之类的操作或类似的操作,因为它可能不会在你希望的时候准确地被调用),但总是回到那个从/到的时间.

Likewise, make sure your timer handler is not dependent upon the exact timing that the handling selector is called (e.g. do not do anything like seconds++ or anything like that because it may not be called precisely when you hope it will), but always go back to that from/to time.

这是一个倒数计时器的示例,它说明我们不计数"任何东西.我们也不关心 appDidEnterBackgroundappDidBecomeActive 之间经过的时间.只需保存停止时间,然后计时器处理程序只需将目标 stopTime 与当前时间进行比较,并根据需要显示经过的时间.

Here is an example of a count-down timer, which illustrates that we don't "count" anything. Nor do we care about the time elapsed between appDidEnterBackground and appDidBecomeActive. Just save the stop time and then the timer handler just compares the target stopTime and the current time, and shows the elapsed time however you'd like.

例如:

import UIKit
import UserNotifications

private let stopTimeKey = "stopTimeKey"

class ViewController: UIViewController {

    @IBOutlet weak var datePicker: UIDatePicker!
    @IBOutlet weak var timerLabel: UILabel!

    private weak var timer: Timer?
    private var stopTime: Date?

    let dateComponentsFormatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.hour, .minute, .second]
        formatter.unitsStyle = .positional
        formatter.zeroFormattingBehavior = .pad
        return formatter
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        registerForLocalNotifications()

        stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
        if let time = stopTime {
            if time > Date() {
                startTimer(time, includeNotification: false)
            } else {
                notifyTimerCompleted()
            }
        }
    }

    @IBAction func didTapStartButton(_ sender: Any) {
        let time = datePicker.date
        if time > Date() {
            startTimer(time)
        } else {
            timerLabel.text = "timer date must be in future"
        }
    }
}

// MARK: Timer stuff

private extension ViewController {
    func registerForLocalNotifications() {
        if #available(iOS 10, *) {
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
                guard granted, error == nil else {
                    // display error
                    print(error ?? "Unknown error")
                    return
                }
            }
        } else {
            let types: UIUserNotificationType = [.alert, .sound, .badge]
            let settings = UIUserNotificationSettings(types: types, categories: nil)
            UIApplication.shared.registerUserNotificationSettings(settings)
        }
    }

    func startTimer(_ stopTime: Date, includeNotification: Bool = true) {
        // save `stopTime` in case app is terminated

        UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
        self.stopTime = stopTime

        // start Timer

        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)

        guard includeNotification else { return }

        // start local notification (so we're notified if timer expires while app is not running)

        if #available(iOS 10, *) {
            let content = UNMutableNotificationContent()
            content.title = "Timer expired"
            content.body = "Whoo, hoo!"
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: stopTime.timeIntervalSinceNow, repeats: false)
            let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
            UNUserNotificationCenter.current().add(notification)
        } else {
            let notification = UILocalNotification()
            notification.fireDate = stopTime
            notification.alertBody = "Timer finished!"
            UIApplication.shared.scheduleLocalNotification(notification)
        }
    }

    func stopTimer() {
        timer?.invalidate()
    }

    // I'm going to use `DateComponentsFormatter` to update the
    // label. Update it any way you want, but the key is that
    // we're just using the scheduled stop time and the current
    // time, but we're not counting anything. If you don't want to
    // use `DateComponentsFormatter`, I'd suggest considering
    // `Calendar` method `dateComponents(_:from:to:)` to
    // get the number of hours, minutes, seconds, etc. between two
    // dates.

    @objc func handleTimer(_ timer: Timer) {
        let now = Date()

        if stopTime! > now {
            timerLabel.text = dateComponentsFormatter.string(from: now, to: stopTime!)
        } else {
            stopTimer()
            notifyTimerCompleted()
        }
    }

    func notifyTimerCompleted() {
        timerLabel.text = "Timer done!"
    }
}

顺便说一句,上面还说明了本地通知的使用(以防应用程序当前未运行时计时器到期).

By the way, the above also illustrates the use of a local notification (in case the timer expires while the app isn't currently running).

对于 Swift 2 版本,请参阅此答案的上一版本.

For Swift 2 rendition, see previous revision of this answer.

这篇关于快速 NSTimer 在后台的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆