J'ai deux minuteries qui s'exécutent à des intervalles de temps différents, dont l'un est ajouté à une boucle d'exécution.
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in // do some stuff } let timer2 = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in // do some other stuff } RunLoop.current.add(timer2, forMode: RunLoop.Mode.common)
J'ai une condition qui nécessite que la logique d'exécution des deux minuteries ne fonctionne pas en même temps puisqu'ils modifient tous les deux les mêmes structures de données.
Est-ce déjà garanti par iOS? Sinon, quelle est la meilleure façon de m'en assurer?
4 Réponses :
Si vous planifiez les deux minuteries, rien ne garantit qu'elles seront déclenchées en même temps.
Vous pouvez vous en assurer en programmant la minuterie2 dans le bloc de feu de la minuterie1, etc.
Tant que vous programmez les minuteries sur le même RunLoop
, vous êtes en sécurité. Un RunLoop
est toujours associé à exactement un Thread et donc les blocs seront exécutés sur ce Thread. Par conséquent, ils ne tireront jamais exactement au même moment.
Les choses seront différentes, cependant, si vous les planifiez sur deux instances RunLoop
différentes.
Je ne suis pas sûr de ce que vous voulez dire lorsque vous dites qu'un seul minuteur est ajouté à un RunLoop
. Si vous n'ajoutez pas de minuterie à un RunLoop
, il ne se déclenchera pas du tout.
Les deux sont ajoutés aux runloops, le premier est implicitement ajouté à .default
et le second est ajouté à .common
. Si vous n'ajoutez pas de minuterie à une boucle d'exécution, elle se déclenchera car elle a été implicitement ajoutée à .default
Je comprends que les minuteries sont ajoutées implicitement, d'où ma réponse. Ce que vous appelez .default
, .common
, cependant, ne sont pas des instances de RunLoop
différentes. Ce sont des modes!
Mon erreur, j'ai mal compris, merci d'avoir clarifié.
Si vous devez assurer l'exécution en série des blocs de minuterie quel que soit l'endroit où les minuteries elles-mêmes ont été programmées, vous devez créer votre propre file d'attente en série et répartir le travail de votre minuterie dans celle-ci:
let timerQueue = DispatchQueue(label: "timerQueue") let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in timerQueue.async { // do stuff } } let timer2 = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in timerQueue.async { // do some other stuff } }
Votre code ne correspond pas à ce que vous décrivez: l'API Je recommande d'utiliser Les minuteries sont suspendues après leur création. Vous devez appeler Les propriétés de la minuterie sont déclarées paresseusement pour éviter les options et les fermetures du gestionnaire d'événements ( SchedulerTimer (withTimeInterval
ajoute implicitement le minuteur à la boucle d'exécution. Vous ne devez pas l'ajouter vous-même à la boucle d'exécution. Donc, les deux strong > les minuteries fonctionnent sur la boucle d'exécution.
DispatchSourceTimer
et une DispatchQueue
personnalisée. La file d'attente est en série par défaut et fonctionne comme FIFO (d'abord dans le premier sorti). La file d'attente série garantit que les tâches ne sont pas exécutées en même temps. activate ()
( ou resume ()
sous iOS 9 et inférieur) pour démarrer les minuteries par exemple dans viewDidLoad
class Controller : UIViewController {
let queue = DispatchQueue(label: "myQueue")
lazy var timer1 : DispatchSourceTimer = {
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline:.now() + 1.0, repeating: 1.0)
timer.setEventHandler(handler: eventHandler1)
return timer
}()
lazy var timer2 : DispatchSourceTimer = {
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline:.now() + 0.5, repeating: 0.5)
timer.setEventHandler(handler: eventHandler2)
return timer
}()
override func viewDidLoad() {
super.viewDidLoad()
timer1.activate()
timer2.activate()
}
func eventHandler1() {
DispatchQueue.main.async {
// do stuff on the main thread
}
}
func eventHandler2() {
DispatchQueue.main.async {
// do stuff on the main thread
}
}
}
() -> Void
) sont implémentées en tant que méthodes standard.
re "Vous ne devez pas ajouter vous-même la minuterie au runloop.", la seule raison pour laquelle je l'ai fait est que je voulais que cette logique s'exécute lorsque les gens font défiler l'application, alors je l'ai ajoutée au .common code> runloop hackingwithswift.com/articles/117/the-ultimate -guide-à-temps r
Veuillez lire attentivement l'article lié. Si vous avez besoin de contrôler la boucle d'exécution, vous devez utiliser l'API non planifiée ( Timer (timeInterval: ...
)
Bonne prise, j'ai raté ça.
Néanmoins, DispatchSourceTimer
est plus précis, plus fiable et plus indépendant
Gotcha - maintenant je rencontre l'erreur ... doit être utilisée à partir du thread principal uniquement
, y a-t-il un moyen de contourner cela en utilisant votre solution?
UITableView.indexPathsForVisibleRows doit être utilisé à partir du thread principal uniquement
et UIScrollView.contentOffset doit être utilisé à partir du thread principal uniquement
Je vois, vous devez exécuter des éléments liés à l'interface utilisateur dans les gestionnaires d'événements sur le thread principal. S'il vous plaît voir la modification
Merci beaucoup. Annuler la minuterie est le meilleur moyen timer1.cancel ()
? De plus, maintenant que les gestionnaires sont asynchrones, cela affecte-t-il les tâches qui ne s'exécutent pas en même temps?
Ça dépend. Si vous voulez juste arrêter un chronomètre une fois, annuler
convient. Mais si vous voulez démarrer et arrêter les minuteries de manière dynamique, déclarez les propriétés facultatives et ajoutez une logique pour contrôler le démarrage et l'arrêt des minuteries de manière fiable.
Gotcha merci. J'ai également modifié le commentaire après l'avoir publié, mais maintenant que les gestionnaires sont asynchrones, cela affecte-t-il les tâches qui ne s'exécutent pas en même temps?
La file d'attente principale
est également en série.
Comment avez-vous ajouté le premier minuteur?