简体   繁体   English

模拟器是否会像物理设备一样杀死不结束后台任务的应用程序?

[英]Does the Simulator kill off apps that don't end background tasks just like a physical device does?

I'm working on debugging why a count down timer is being killed off in the background.我正在调试为什么倒计时计时器在后台被杀死。

To familiarize myself better, I made a quick and dirty timer implementation.为了更好地熟悉自己,我做了一个快速而肮脏的计时器实现。 It starts a background task, starts a standard timer, and adds it to the RunLoop.它启动一个后台任务,启动一个标准计时器,并将其添加到 RunLoop。

Whenever the seconds change on the count down, I print out how many seconds on the count down I have left and how many seconds the OS has given me (ie UIApplication.shared.backgroundTimeRemaining ).每当倒计时的秒数发生变化时,我都会打印出我还剩多少秒以及操作系统给了我多少秒(即UIApplication.shared.backgroundTimeRemaining )。

However, when I run this in the simulator, start the timer, and put the app in the background, the timer works just fine and never stops until it counted all the way down.但是,当我在模拟器中运行它,启动计时器并将应用程序置于后台时,计时器工作得很好并且永远不会停止,直到它一直倒计时。

Some things to keep in mind:需要记住的一些事项:

I WANT the timer to not stop until done, even if in background.我希望计时器在完成之前不会停止,即使在后台也是如此。 However, I know the OS typically gives 3-5 mins in the background.但是,我知道操作系统通常会在后台提供 3-5 分钟。 This is where my question comes from.这就是我的问题的来源。 If I only have 3-5 mins in the background, then why is my timer running basically as long as it needs to?如果我在后台只有 3-5 分钟,那为什么我的计时器基本上只要它需要的时间就运行? Does the Simulator not kill off apps in the same time as the physical device in the background?模拟器不会在后台关闭物理设备的同时关闭应用程序吗?

Additionally, I also set a callback to fire when and if the OS kills my off (ie the expirationHandler callback provided by the beginBackgroundTask(withName: function)此外,我还设置了一个回调以在操作系统杀死我时触发(即beginBackgroundTask(withName: function) 提供的expirationHandler回调

Any insight into this would be helpful: Here's my View Controller class:对此的任何见解都会有所帮助:这是我的视图 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
    }
}

Here's are some screenshots of the output (one from the start of the count down, one from when the expirationHandler was fired, one from the end of the count down):下面是 output 的一些截图(一张从倒计时开始,一张从 expireHandler 被触发时,一张从倒计时结束):

当倒计时结束时

倒计时计时器启动时

当 expireHandler 被触发时

Well, I figured it out, Stupidly enough.好吧,我想通了,够愚蠢的。 I should have just tried it on a physical device (just didn't have one at the time of posting that question).我应该只是在物理设备上尝试过(只是在发布该问题时没有)。

Findings:发现:

The simulator does NOT behave in the same way as the physical device because it does NOT kill off apps after the allowed time from the OS is up (ie the UIApplication.shared.backgroundTimeRemaining is basically a lie/non issue when in the simulator).模拟器的行为方式与物理设备不同,因为它不会在操作系统允许的时间结束后终止应用程序(即,在模拟器中时,UIApplication.shared.backgroundTimeRemaining 基本上是一个谎言/非问题)。

On the other hand, with a physical device, as soon as backgroundTimeRemaining hit 0, the app was killled off which was expected.另一方面,对于物理设备,一旦 backgroundTimeRemaining 达到 0,应用程序就会被终止,这是意料之中的。 Here's a screenshot of the output:这是output的截图:

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM