Fai qualcosa ogni x minuti in Swift


113

Come posso eseguire una funzione ogni minuto? In JavaScript posso fare qualcosa del genere setInterval, esiste qualcosa di simile in Swift?

Uscita desiderata:

Hello World una volta al minuto ...


Aggiornato per Swift 2: Swift Timer
JamesG

Risposte:


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

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

Ricorda di importare Foundation.

Swift 4:

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

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

1
ma cosa succede se si desidera passare da una visualizzazione all'altra? Il codice si fermerà, giusto?
Cing

5
@Cing dipende da cosa si riferisce.
Antzi

1
Non dimenticare che NSTimermantiene il suo obiettivo, quindi, con questa configurazione, se helloWorldTimerè una proprietà su selfhai un ciclo di conservazione, dove selftrattiene helloWorldTimere helloWorldTimertrattiene self.
MANIAK_dobrii

@MANIAK_dobrii Puoi anche commentare come interrompere il ciclo di conservazione? Se il VC viene ignorato ma il timer non viene annullato, il ciclo è ancora intatto? Quindi dovete entrambi annullare il timer e poi chiudere la visualizzazione per interrompere il ciclo?
Dave G

1
Per Swift 4, il metodo di cui si desidera ottenere il selettore deve essere esposto a Objective-C, quindi l'attributo @objc deve essere aggiunto alla dichiarazione del metodo. ad esempio `` `@objc func sayHello () {}` `` `
Shamim Hossain

136

Se scegli come target iOS versione 10 e successive, puoi utilizzare la resa basata su blocchi di Timer, che semplifica i potenziali cicli di riferimento forti, ad esempio:

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()
}

Sebbene Timersia generalmente il migliore, per ragioni di completezza, dovrei notare che puoi anche usare il timer di invio, che è utile per programmare i timer sui thread in background. Con i timer di invio, poiché sono basati su blocchi, evita alcune delle forti sfide del ciclo di riferimento con il vecchio modello target/ , purché utilizzi i riferimenti.selectorTimerweak

Così:

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()
}

Per ulteriori informazioni, vedere la sezione Creazione di un timer di Esempi di origini di invio nella sezione Origini di invio della Guida alla programmazione della concorrenza.


Per Swift 2, vedere la revisione precedente di questa risposta .


come sceglieresti un timer di esecuzione una sola volta all'interno di questo approccio?
Juan Boero

@JuanPabloBoero - Non userei questo approccio per situazioni di fuoco una volta. Useresti dispatch_after. O un non ripetitivo NSTimer.
Rob

@ Rob Ho un'etichetta contatore sullo schermo che viene aggiornata ogni secondo da una funzione e sto usando il codice sopra per aumentare il mio contatore e aggiornare il testo dell'etichetta. Ma il problema che sto affrontando è che la mia variabile contatore viene aggiornata ogni secondo, ma lo schermo non mostra l'ultimo valore del contatore nella mia UILabel.
Nagendra Rao

Rao, quanto sopra è utile per eseguire alcune azioni su un thread in background. Ma gli aggiornamenti dell'interfaccia utente devono avvenire nella coda principale. Quindi, pianificalo sul thread principale o almeno invia gli aggiornamenti dell'interfaccia utente al thread principale.
Rob l'

1
Questa domanda non appartiene qui nei commenti a questa risposta. Elimina i tuoi commenti sopra e pubblica la tua domanda. Ma, in breve, generalmente non mantieni l'app in esecuzione in background, ma fai solo sembrare che lo fosse. Vedi stackoverflow.com/a/31642036/1271826 .
Rob l'

17

Ecco un aggiornamento alla NSTimerrisposta, per Swift 3 (in cui è NSTimerstato rinominato in Timer) utilizzando una chiusura anziché una funzione denominata:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}

6
questo è solo per iOS 10.
Glenn,

per favore non pubblicare soluzioni
iOS

13

Se puoi consentire un po 'di tempo, ecco una semplice soluzione eseguendo del codice ogni minuto:

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

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

Esegui solo executeRepeatedly()una volta e verrà eseguito ogni minuto. L'esecuzione si interrompe quando selfviene rilasciato l'oggetto proprietario ( ). È inoltre possibile utilizzare un flag per indicare che l'esecuzione deve essere interrotta.


Questa decisione è molto più conveniente che avere in giro tutti questi timer, aggiungerli ai runloops, invalidarli ... Fantastico!
julia_v

questa sembra una soluzione valida ma sta creando un ciclo di conservazione quando lo uso con la mia chiamata al servizio web ...
jayant rawat

11

Puoi usare Timer(swift 3)

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

In selector () inserisci il nome della tua funzione


Come puoi farlo in Swift 3?
bibscy

1
use Timer... NSTimerè stato ribattezzato
Joseph il

7

In swift 3.0 il GCD è stato refactoring:

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

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

Ciò è particolarmente utile quando è necessario eseguire l'invio su una particolare coda. Inoltre, se hai intenzione di utilizzarlo per l'aggiornamento dell'interfaccia utente, ti suggerisco di controllare CADisplayLinkpoiché è sincronizzato con la frequenza di aggiornamento della GPU.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.