Подтвердить что ты не робот

Простое обслуживание в Swift, сохраняется, только переднем плане?

Мне очень просто нужно что-то запустить

  • каждые 20 секунд
  • просто, только когда приложение находится на переднем плане (явно не выполняется в фоновом режиме)
  • очевидно, было бы более элегантно, если вам не нужно беспокоиться о том, что приложение входит и выходит из переднего плана.
  • Проблема, я был поражен, узнав с помощью scheduleRepeating, что в симуляторе он продолжает работать, когда приложение находится в фоновом режиме: это не дает мне уверенности в том, что он явно не будет работать в фоновом режиме (некоторые? какие?) устройства
  • Проблема, я обнаружил, что на устройстве, тестируя как можно больше поколений/ОС, scheduleRepeating раздражает (в некоторых случаях?), запускается один раз, только когда приложение переходит в фоновый режим. То есть: он запускается "еще раз", как только приложение переходит на задний план. Соси.

Итак, чтобы повторить: в iOS, как иметь простой сервис, который запускается каждые 20 секунд и запускается только тогда, когда приложение находится на переднем плане, и это бесполезно. В идеале вам не нужно перезапускать это, проверить его или что-нибудь в течение жизни приложения...

действительно лучший способ сделать такую ​​простую вещь?

Это мое (казалось бы) рабочее решение, которое неэлегантно. Разумеется, есть встроенный способ или что-то еще, чтобы сделать такую ​​очевидную вещь в iOS?

open class SomeDaemon {

    static let shared = SomeDaemon()
    fileprivate init() { print("SomeDaemon is running") }
    var timer: DispatchSourceTimer? = nil
    let gap = 10   // seconds between runs

    func launch() {

        // you must call this from application#didLaunch
        // harmless if you accidentally call more than once
        // you DO NOT do ANYTHING on app pause/unpause/etc

        if timer != nil {

            print("SomeDaemon, timer running already")
            return
        }

        timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
        timer?.scheduleRepeating(deadline: .now(), interval: .seconds(gap))
        timer?.setEventHandler{ self. doSomeThing() }
        timer?.resume()
    }

    private func doSomeThing() {

        if UIApplication.shared.applicationState != .active {

            print("avoided infuriating 'runs once more in bg' problem.")
            return
        }

        // actually do your thing here
    }
}
4b9b3361

Ответ 1

За кулисами:

Это не великолепно, но хорошо работает хорошо.

struct ForegroundTimer {
    static var sharedTimerDelegate: ForegroundTimerDelegate?
    static var sharedTimer = Timer()

    private var timeInterval: Double = 20.0

    static func startTimer() {
        ForegroundTimer.waitingTimer.invalidate()

        var wait = 0.0
        if let time = ForegroundTimer.startedTime {
            wait = timeInterval - (Date().timeIntervalSince1970 - time).truncatingRemainder(dividingBy: Int(timeInterval))
        } else {
            ForegroundTimer.startedTime = Date().timeIntervalSince1970
        }
        waitingTimer = Timer.scheduledTimer(withTimeInterval: wait, repeats: false) { (timer) in
            ForegroundTimer.sharedTimerDelegate?.timeDidPass()
            ForegroundTimer.sharedTimer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true, block: { (timer) in
                ForegroundTimer.sharedTimerDelegate?.timeDidPass()
            })
        }
    }

    static func pauseTimer() {
        ForegroundTimer.sharedTimer.invalidate()
    }

    private static var startedTime: TimeInterval?
    private static var waitingTimer = Timer()
}

protocol ForegroundTimerDelegate {
    func timeDidPass()
}

Этот код выше готов к использованию как есть. Конечно, вы можете timeInterval на любой промежуток времени, который вы хотите.

Использование прост:

В AppDelegate.swift есть два метода:

func applicationDidBecomeActive(_ application: UIApplication) {
    ForegroundTimer.startTimer()
}

func applicationDidEnterBackground(_ application: UIApplication) {
    ForegroundTimer.pauseTimer()
}

Затем, любой класс, который вы хотите "прослушать" для инструмента таймера ForegroundTimerDelegate, и потребовал метод, timeDidPass(). Наконец, присвойте этому классу /self значение ForegroundTimer.sharedTimerDelegate. Например:

class ViewController: UIViewController, ForegroundTimerDelegate {
    func viewDidLoad() {
        ForegroundTimer.sharedTimerDelegate = self
    }

    func timeDidPass() {
        print("20 in-foreground seconds passed")
    }
}

Надеюсь, это поможет!

Ответ 2

Ответ на

@Dopapp довольно солидный, и я бы пошел с ним, но если вы хотите, чтобы он еще проще, вы можете попробовать что-то с RunLoop:

let timer = Timer.scheduledTimer(withTimeInterval: wait, repeats: true) { _ in print("time passed") }
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)

Это даже даст вам некоторые функциональные возможности для выполнения фона.

ПРЕДУПРЕЖДЕНИЕ: код не проверен!