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?
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:
weakvar 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){[weakself]_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 {[weakself]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.
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")}
Si vous pouvez permettre une dérive temporelle, voici une solution simple exécutant du code toutes les minutes:
privatefunc executeRepeatedly(){// put your code here
DispatchQueue.main.asyncAfter(deadline:.now()+60.0){[weakself]inself?.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.
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.
Réponses:
N'oubliez pas d'importer Foundation.
Swift 4:
la source
NSTimer
conserve sa cible, donc, avec cette configuration, s'ilhelloWorldTimer
s'agit d'une propriété,self
vous avez vous-même un cycle de conservation, oùself
conservehelloWorldTimer
ethelloWorldTimer
conserveself
.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:Bien que ce
Timer
soit 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'ancientarget
/selector
modèle deTimer
, tant que vous utilisez desweak
références.Alors:
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 .
la source
dispatch_after
. Ou un non-répétitifNSTimer
.Voici une mise à jour de la
NSTimer
réponse, pour Swift 3 (dans lequel aNSTimer
été renommé enTimer
) en utilisant une fermeture plutôt qu'une fonction nommée:la source
Si vous pouvez permettre une dérive temporelle, voici une solution simple exécutant du code toutes les minutes:
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.la source
Vous pouvez utiliser
Timer
(swift 3)Dans selector () vous mettez le nom de votre fonction
la source
Timer
...NSTimer
a été renomméDans Swift 3.0, le GCD a été remanié:
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
CADisplayLink
car il est synchronisé avec le taux de rafraîchissement du GPU.la source