Убивает ли симулятор приложения, которые не завершают фоновые задачи, как это делает физическое устройство? - PullRequest
0 голосов
/ 22 октября 2019

Я работаю над отладкой, почему таймер обратного отсчета отключается в фоновом режиме.

Чтобы лучше ознакомиться, я сделал быструю и грязную реализацию таймера. Он запускает фоновую задачу, запускает стандартный таймер и добавляет его в 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, один с конца обратного отсчета):

When the count down timer ended

When the count down timer was started

When the expirationHandler was fired

1 Ответ

0 голосов
/ 22 октября 2019

Ну, я понял это! Достаточно глупо, я должен был просто попробовать его на физическом устройстве (просто не было на момент публикации этого вопроса).

Выводы:

симулятор НЕ ведет себя так же, как физическое устройство, потому что он НЕ убивает приложения после истечения разрешенного времени от ОС (т. е. UIApplication.shared.backgroundTimeRemaining является в основном ложной / несуществующей проблемой в симуляторе).

С другой стороны, с физическим устройством, как только backgroundTimeRemaining достигнет 0, приложение было убито, что и ожидалось. Вот снимок экрана с выводом:

enter image description here

...