我正在调试为什么倒计时计时器在后台被杀死。
为了更好地熟悉自己,我制作了一个快速而肮脏的计时器实现。它启动后台任务,启动标准计时器,并将其添加到RunLoop。
[每当倒数秒数改变时,我就会打印出剩余的秒数和操作系统给我的秒数(即UIApplication.shared.backgroundTimeRemaining
)。
但是,当我在模拟器中运行此程序,启动计时器并将应用程序置于后台时,计时器可以正常工作,并且一直停下来直到它一直倒数为止。
一些注意事项:
我希望计时器不停止直到完成,即使在后台也是如此。但是,我知道操作系统通常会在后台提供3-5分钟。这是我的问题的出处。如果我在后台只有3-5分钟,那为什么我的计时器基本上可以按需运行呢?模拟器不会在后台与物理设备同时杀死应用程序吗?
此外,我还设置了一个回调,以便在操作系统杀死我的情况时触发(即expirationHandler
函数提供的beginBackgroundTask(withName:
回调)
对此有任何见识都会有所帮助!这是我的View Controller类:
class TimerViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
// MARK: - Outlets
@IBOutlet weak var timeLeftLabel: UILabel!
@IBOutlet weak var timePicker: UIPickerView!
// MARK: - Properties
var timer: Timer?
var timeLeft: Int = 0 {
didSet {
DispatchQueue.main.async {
self.timeLeftLabel.text = "\(self.timeLeft.description) seconds left"
}
}
}
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
let backgroundTaskName = "bgTask"
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
@objc func applicationDidMoveToBackground() {
print("moved to backgorund")
}
@objc func applicationWillMoveToForegraund() {
print("moved to foreground")
}
// MARK: - Setup
func setupUI() {
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidMoveToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillMoveToForegraund), name: UIApplication.willEnterForegroundNotification, object: nil)
timePicker.tintColor = .white
timePicker.backgroundColor = .clear
}
func registerBackgroundTask() {
//end any bg tasks
endBackgroundTask()
//start new one
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: backgroundTaskName, expirationHandler: {
//times up, do this stuff when ios kills me
print("background task being ended by expiration handler")
self.endBackgroundTask()
})
assert(backgroundTask != UIBackgroundTaskIdentifier.invalid)
//actual meat of bg task
print("starting")
timePicker.isHidden = true
timeLeftLabel.isHidden = false
timeLeft = getCurrentPickerViewSeconds()
timeLeftLabel.text = "\(timeLeft) seconds left"
setupTimer()
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = .invalid
}
func setupTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fire), userInfo: nil, repeats: true)
if timer != nil {
RunLoop.current.add(timer!, forMode: .common)
} else {
print("timer is nil, didnt add to runloop")
}
}
// MARK: - Helpers
func getCurrentPickerViewSeconds() -> Int {
let mins = timePicker.selectedRow(inComponent: 0)
let seconds = timePicker.selectedRow(inComponent: 1)
let totalSeconds = seconds + (mins * 60)
return totalSeconds
}
// MARK: - Actions
@objc func fire() {
print("current time left: \(timeLeft)")
print("background time remaining: \(UIApplication.shared.backgroundTimeRemaining)")
if timeLeft > 0 {
timeLeft -= 1
} else {
print("done")
stopTimer()
}
}
@IBAction func startTimer() {
registerBackgroundTask()
}
@IBAction func stopTimer() {
print("stopping")
endBackgroundTask()
timePicker.isHidden = false
timeLeftLabel.isHidden = true
timer?.invalidate()
timer = nil
}
@IBAction func resetTimer() {
print("resetting")
stopTimer()
startTimer()
}
@IBAction func doneTapped() {
print("done-ing")
stopTimer()
dismiss(animated: true, completion: nil)
}
// MARK: - Picker View
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 59
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
var label = ""
switch component {
case 0:
label = "m"
case 1:
label = "s"
default:
label = ""
}
let result = "\(row) \(label)"
let attributedResult = NSAttributedString(string: result, attributes: [NSAttributedString.Key.foregroundColor : UIColor.white])
return attributedResult
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let seconds = row + (60 * component)
timeLeft = seconds
}
}
这是输出的一些屏幕截图(一个从倒数开始的屏幕截图,一个从触发expirationHandler触发时的屏幕截图,一个从倒数结束时的屏幕截图:]]
我正在调试为什么倒数计时器在后台被杀死。为了更好地熟悉自己,我制作了一个快速而肮脏的计时器实现。它启动后台任务,启动...
嗯,我知道了!足够愚蠢的是,我应该只在物理设备上尝试过(在发布该问题时才没有)。