Haz algo cada x minutos en Swift

113

¿Cómo puedo ejecutar una función cada minuto? En JavaScript puedo hacer algo como setInterval, ¿existe algo similar en Swift?

Salida deseada:

Hola mundo una vez por minuto ...

JOSEFtw
fuente
Actualizado para Swift 2: Swift Timer
JamesG

Respuestas:

171
var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)

func sayHello() 
{
    NSLog("hello World")
}

Recuerde importar Foundation.

Rápido 4:

 var helloWorldTimer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(ViewController.sayHello), userInfo: nil, repeats: true)

 @objc func sayHello() 
 {
     NSLog("hello World")
 }
Unheilig
fuente
1
pero ¿qué pasa si quieres cambiar entre vistas? El código se detendrá, ¿verdad?
Cing
5
@Cing depende de a qué se refiera.
Antzi
1
No olvide que NSTimerretiene su objetivo, por lo que, con esta configuración, si helloWorldTimeres una propiedad self, tiene un ciclo de retención, donde selfretiene helloWorldTimery helloWorldTimerretiene self.
MANIAK_dobrii
@MANIAK_dobrii ¿Puedes comentar también cómo romper el ciclo de retención? Si el VC se desconecta pero el temporizador no se cancela, ¿el ciclo aún está intacto? Entonces, ¿deben cancelar el temporizador y luego cerrar la vista para romper el ciclo?
Dave G
1
Para Swift 4, el método del que desea obtener el selector debe estar expuesto a Objective-C, por lo que el atributo @objc debe agregarse a la declaración del método. por ejemplo, `` `@objc func sayHello () {}` ``
Shamim Hossain
136

Si tiene como objetivo la versión 10 de iOS y superior, puede usar la interpretación basada en bloques de Timer, que simplifica los posibles ciclos de referencia fuertes, por ejemplo:

weak var timer: Timer?

func startTimer() {
    timer?.invalidate()   // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it
    timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
        // do something here
    }
}

func stopTimer() {
    timer?.invalidate()
}

// if appropriate, make sure to stop your timer in `deinit`

deinit {
    stopTimer()
}

Si bien en Timergeneral es lo mejor, en aras de la integridad, debo señalar que también puede usar el temporizador de envío, que es útil para programar temporizadores en subprocesos en segundo plano. Con los temporizadores de despacho, dado que están basados ​​en bloques, evita algunos de los fuertes desafíos del ciclo de referencia con el antiguo target/ selectorpatrón de Timer, siempre y cuando useweak referencias.

Entonces:

var timer: DispatchSourceTimer?

func startTimer() {
    let queue = DispatchQueue(label: "com.domain.app.timer")  // you can also use `DispatchQueue.main`, if you want
    timer = DispatchSource.makeTimerSource(queue: queue)
    timer!.schedule(deadline: .now(), repeating: .seconds(60))
    timer!.setEventHandler { [weak self] in
        // do whatever you want here
    }
    timer!.resume()
}

func stopTimer() {
    timer?.cancel()
    timer = nil
}

deinit {
    self.stopTimer()
}

Para obtener más información, consulte la sección Creación de un temporizador de Ejemplos de fuentes de despacho en la sección Fuentes de despacho de la Guía de programación de simultaneidad.


Para Swift 2, consulte la revisión anterior de esta respuesta .

Robar
fuente
¿Cómo elegiría un temporizador de ejecución única dentro de este enfoque?
Juan Boero
@JuanPabloBoero - No usaría este enfoque para situaciones de fuego único. Usarías dispatch_after. O un no repetitivo NSTimer.
Rob
@Rob Tengo una etiqueta de contador en la pantalla que se actualiza cada segundo desde una función, y estoy usando el código anterior para incrementar mi contador y actualizar el texto de la etiqueta. Pero, el problema al que me enfrento es que mi variable de contador se actualiza cada segundo, pero la pantalla no muestra el último valor de contador en mi UILabel.
Nagendra Rao
Rao, lo anterior es útil para realizar alguna acción en un hilo de fondo. Pero las actualizaciones de la interfaz de usuario deben realizarse en la cola principal. Por lo tanto, programe esto en el hilo principal o al menos envíe las actualizaciones de la interfaz de usuario al hilo principal.
Rob
1
Esta pregunta no pertenece aquí en los comentarios a esta respuesta. Elimine sus comentarios anteriores y publique su propia pregunta. Pero, en resumen, generalmente no mantiene la aplicación ejecutándose en segundo plano, sino que solo hace que parezca que lo estaba. Consulte stackoverflow.com/a/31642036/1271826 .
Rob
17

Aquí hay una actualización de la NSTimerrespuesta, para Swift 3 (en el que NSTimerse cambió el nombre a Timer) usando un cierre en lugar de una función con nombre:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}
John T
fuente
6
eso es solo para iOS 10.
Glenn
por favor, no publique soluciones ios 10+
Numan Karaaslan
13

Si puede permitir un cierto tiempo de deriva, aquí hay una solución simple que ejecuta un código cada minuto:

private func executeRepeatedly() {
    // put your code here

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { [weak self] in
        self?.executeRepeatedly()
    }
}

Ejecútelo executeRepeatedly()una vez y se ejecutará cada minuto. La ejecución se detiene cuando selfse libera el objeto propietario ( ). También puede usar una bandera para indicar que la ejecución debe detenerse.

algrid
fuente
Esta decisión es mucho más conveniente que tener todos estos temporizadores, agregarlos a runloops, invalidarlos ... ¡Genial!
julia_v
esto parece una solución válida, pero está creando un ciclo de retención cuando lo estoy usando con mi llamada de servicio web ...
jayant rawat
11

Puedes usar Timer(swift 3)

var timer = Timer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("function"), userInfo: nil, repeats: true)

En selector () pones el nombre de tu función

Bas
fuente
¿Cómo puedes hacerlo en Swift 3?
bibscy
1
use Timer... NSTimerha cambiado de nombre
Joseph
7

En Swift 3.0, el GCD se refactorizó:

let timer : DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)

timer.scheduleRepeating(deadline: .now(), interval: .seconds(60))
timer.setEventHandler
{
    NSLog("Hello World")
}
timer.resume()

Esto es especialmente útil para cuando necesita enviar en una Cola en particular. Además, si planea usar esto para la actualización de la interfaz de usuario, le sugiero que investigue CADisplayLinkya que está sincronizado con la frecuencia de actualización de la GPU.

Poder
fuente