Faites quelque chose toutes les x minutes dans Swift

113

Comment puis-je exécuter une fonction toutes les minutes? En JavaScript, je peux faire quelque chose comme setInterval, est-ce que quelque chose de similaire existe dans Swift?

Sortie recherchée:

Hello World une fois par minute ...

JOSEFtw
la source
Mise à jour pour Swift 2: Swift Timer
JamesG

Réponses:

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

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

N'oubliez pas d'importer 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")
 }
Unheilig
la source
1
mais que faire si vous voulez basculer entre les vues? Le code s'arrêtera, non?
Cing
5
@Cing dépend de ce à quoi il fait référence.
Antzi
1
N'oubliez pas que NSTimerconserve sa cible, donc, avec cette configuration, s'il helloWorldTimers'agit d'une propriété, selfvous avez vous-même un cycle de conservation, où selfconserve helloWorldTimeret helloWorldTimerconserve self.
MANIAK_dobrii
@MANIAK_dobrii Pouvez-vous également commenter comment briser le cycle de rétention? Si le VC est rejeté mais que la minuterie n'est pas annulée, le cycle est-il toujours intact? Vous devez donc à la fois annuler la minuterie et ensuite fermer la vue pour interrompre le cycle?
Dave G
1
Pour Swift 4, la méthode dont vous voulez obtenir le sélecteur doit être exposée à Objective-C, donc l'attribut @objc doit être ajouté à la déclaration de méthode. par exemple `` `` @objc func sayHello () {} `` ``
Shamim Hossain
136

Si vous ciblez iOS version 10 et supérieure, vous pouvez utiliser le rendu basé sur des blocs Timer, ce qui simplifie les cycles de référence potentiels forts, par exemple:

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

Bien que ce Timersoit généralement le meilleur, par souci d'exhaustivité, je dois noter que vous pouvez également utiliser la minuterie de répartition, ce qui est utile pour planifier des minuteries sur les threads d'arrière-plan. Avec les temporisateurs de répartition, puisqu'ils sont basés sur des blocs, cela évite certains des défis forts du cycle de référence avec l'ancien target/ selectormodèle de Timer, tant que vous utilisez des weakréférences.

Alors:

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

Pour plus d'informations, consultez la section Création d'un minuteur des exemples de sources de répartition dans la section Sources de répartition du Guide de programmation d'accès concurrentiel.


Pour Swift 2, voir la révision précédente de cette réponse .

Rob
la source
Comment opteriez-vous pour un minuteur d'exécution unique dans cette approche?
Juan Boero
@JuanPabloBoero - Je n'utiliserais pas cette approche pour une situation d'incendie unique. Vous utiliseriez dispatch_after. Ou un non-répétitif NSTimer.
Rob
@Rob J'ai une étiquette de compteur à l'écran qui est mise à jour toutes les secondes à partir d'une fonction, et j'utilise le code ci-dessus pour incrémenter mon compteur et mettre à jour le texte de l'étiquette. Mais le problème auquel je suis confronté est que ma variable de compteur est mise à jour toutes les secondes, mais l'écran n'affiche pas la dernière valeur de compteur dans mon UILabel.
Nagendra Rao
Rao, ce qui précède est utile pour effectuer une action sur un thread d'arrière-plan. Mais les mises à jour de votre interface utilisateur doivent se produire sur la file d'attente principale. Donc, planifiez cela sur le thread principal ou au moins renvoyez les mises à jour de l'interface utilisateur au thread principal.
Rob
1
Cette question n'appartient pas ici dans les commentaires à cette réponse. Supprimez vos commentaires ci-dessus et postez votre propre question. Mais, en bref, vous ne maintenez généralement pas l'application en arrière-plan, mais vous la faites seulement ressembler à ce qu'elle était. Voir stackoverflow.com/a/31642036/1271826 .
Rob
17

Voici une mise à jour de la NSTimerréponse, pour Swift 3 (dans lequel a NSTimerété renommé en Timer) en utilisant une fermeture plutôt qu'une fonction nommée:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}
John T
la source
6
c'est pour iOS 10 uniquement.
Glenn
s'il vous plaît ne postez pas ios 10+ solutions
Numan Karaaslan
13

Si vous pouvez permettre une dérive temporelle, voici une solution simple exécutant du code toutes les minutes:

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

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

Exécutez juste executeRepeatedly()une fois et il sera exécuté chaque minute. L'exécution s'arrête lorsque l'objet propriétaire ( self) est libéré. Vous pouvez également utiliser un indicateur pour indiquer que l'exécution doit s'arrêter.

algrid
la source
Cette décision est bien plus pratique que d'avoir autour de tous ces minuteries, de les ajouter aux runloops, de les invalider… Cool!
julia_v
cela semble une solution valide, mais cela crée un cycle de rétention lorsque je l'utilise avec mon appel de service Web ...
jayant rawat
11

Vous pouvez utiliser Timer(swift 3)

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

Dans selector () vous mettez le nom de votre fonction

Bas
la source
Comment pouvez-vous le faire dans Swift 3?
bibscy
1
use Timer... NSTimera été renommé
Joseph
7

Dans Swift 3.0, le GCD a été remanié:

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

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

Ceci est particulièrement utile lorsque vous devez distribuer sur une file d'attente particulière. De plus, si vous prévoyez de l'utiliser pour la mise à jour de l'interface utilisateur, je vous suggère de l'examiner CADisplayLinkcar il est synchronisé avec le taux de rafraîchissement du GPU.

Pouvez
la source