eureka - push notifications ios swift tutorial
¿El servicio simple en Swift persiste solo en primer plano? (2)
Entre bastidores:
No es hermoso , pero funciona bastante bien.
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()
}
Este código de arriba está listo para usar tal como está. Por supuesto, puede timeInterval
al intervalo de tiempo que desee.
El uso es simple:
En AppDelegate.swift, tenga estos dos métodos:
func applicationDidBecomeActive(_ application: UIApplication) {
ForegroundTimer.startTimer()
}
func applicationDidEnterBackground(_ application: UIApplication) {
ForegroundTimer.pauseTimer()
}
Luego, tenga la clase que desee ''escuchar'' para el temporizador implementar ForegroundTimerDelegate
, y su método requerido, timeDidPass()
. Finalmente, asigne esa clase / self
a ForegroundTimer.sharedTimerDelegate
. Por ejemplo:
class ViewController: UIViewController, ForegroundTimerDelegate {
func viewDidLoad() {
ForegroundTimer.sharedTimerDelegate = self
}
func timeDidPass() {
print("20 in-foreground seconds passed")
}
}
¡Espero que esto ayude!
Simplemente, quiero que algo funcione
- cada decir 20 segundos
- simplemente solo cuando la aplicación está en primer plano (explícitamente no se ejecuta cuando el fondo)
- obviamente, sería más elegante si no tienes que molestarte en seguir la aplicación entrando y saliendo del primer plano
- El problema, me sorprendió aprender con el
scheduleRepeating
que, en el simulador, sigue funcionando cuando la aplicación está en segundo plano: eso no me da la confianza de que explícitamente no se ejecutará en segundo plano en dispositivos (¿algo?). - El problema es que descubrí que en un dispositivo, probando con tantas generaciones / sistema operativo como sea posible,
scheduleRepeating
molestamente (solo en algunos casos) se ejecutará una sola vez cuando la aplicación entre en segundo plano. Es decir: se ejecuta "una vez más" una vez que la aplicación se pone en segundo plano. Chupar.
Entonces, para repetir: en iOS, cómo tener un servicio simple que se ejecuta cada 20 segundos, y que solo se ejecuta cuando la aplicación está en primer plano, y es sencillo. Lo ideal es que no tenga que reiniciarlo, verifique o algo durante la vida de la aplicación ...
realmente, ¿cuál es la mejor manera de hacer algo tan simple?
Esta es mi (aparentemente) solución de trabajo, que es poco elegante. Seguramente hay una forma incorporada, o algo así, de hacer algo tan obvio en 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
}
}
La respuesta de @Dopapp es bastante sólida y me gustaría ir con eso, pero si lo quieres aún más simple, puedes probar algo con el RunLoop:
let timer = Timer.scheduledTimer(withTimeInterval: wait, repeats: true) { _ in print("time passed") }
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
Esto incluso le dará algunas funcionalidades sobre la ejecución de fondo.
ADVERTENCIA: ¡Código no probado!