web-dev-qa-db-fra.com

UIScrollView suspend NSTimer jusqu'à la fin du défilement

Pendant qu'un UIScrollView (ou une classe dérivée de celui-ci) défile, il semble que tous les NSTimers en cours d'exécution soient mis en pause jusqu'à la fin du défilement.

Y a-t-il un moyen de contourner ceci? Des discussions? Une priorité? N'importe quoi?

83
mcccclean

Une solution facile et simple à mettre en œuvre consiste à:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
198
Kashif Hisam

Pour tous ceux qui utilisent Swift 3

timer = Timer.scheduledTimer(timeInterval: 0.1,
                            target: self,
                            selector: aSelector,
                            userInfo: nil,
                            repeats: true)


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
23
Isaac

Oui, Paul a raison, c'est un problème de boucle d'exécution. Plus précisément, vous devez utiliser la méthode NSRunLoop:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
8
August

Il s'agit de la version Swift.

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
7
Andrew Cook

Vous devez exécuter un autre thread et une autre boucle d'exécution si vous souhaitez que les minuteries se déclenchent pendant le défilement; étant donné que les minuteries sont traitées dans le cadre de la boucle d'événements, si vous êtes occupé à faire défiler votre vue, vous ne passez jamais aux minuteries. Bien que la pénalité de perf/batterie de l'exécution de minuteries sur d'autres threads ne vaille pas la peine d'être traitée dans ce cas.

6
Ana Betts

pour tout le monde, utilisez Swift 4:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)
3
YuLong Xiao

tl; dr le runloop effectue un défilement afin qu'il ne puisse plus gérer d'événements - à moins que vous ne régliez manuellement le minuteur de sorte qu'il puisse également se produire lorsque runloop gère des événements tactiles. Ou essayez une autre solution et utilisez GCD


Une lecture incontournable pour tout développeur iOS. Beaucoup de choses sont finalement exécutées via RunLoop.

Dérivé de documents d'Apple .

Qu'est-ce qu'une boucle Run?

Une boucle de course ressemble beaucoup à son nom. C'est une boucle que votre thread entre et utilise pour exécuter des gestionnaires d'événements en réponse aux événements entrants

Comment la livraison des événements est interrompue?

Étant donné que les temporisateurs et autres événements périodiques sont remis lorsque vous exécutez la boucle d'exécution, le contournement de cette boucle interrompt la remise de ces événements. L'exemple typique de ce comportement se produit chaque fois que vous implémentez une routine de suivi de la souris en entrant une boucle et en demandant à plusieurs reprises des événements à l'application. Étant donné que votre code saisit directement les événements, plutôt que de laisser l'application distribuer ces événements normalement, les temporisateurs actifs ne pourront se déclencher qu'après la fin de votre routine de suivi de la souris et le retour du contrôle à l'application.

Que se passe-t-il si le minuteur est déclenché lorsque la boucle d'exécution est en cours d'exécution?

Cela arrive BEAUCOUP DE FOIS, sans que nous nous en rendions compte. Je veux dire que nous réglons la minuterie pour qu'elle se déclenche à 10: 10: 10: 00, mais le runloop exécute un événement qui prend jusqu'à 10: 10: 10: 05, donc la minuterie est déclenchée 10: 10: 10: 06

De même, si un temporisateur se déclenche lorsque la boucle d'exécution est en train d'exécuter une routine de gestionnaire, le temporisateur attend jusqu'à la prochaine fois dans la boucle d'exécution pour appeler sa routine de gestionnaire. Si la boucle d'exécution ne fonctionne pas du tout, la minuterie ne se déclenche jamais.

Est-ce que le défilement ou quoi que ce soit qui garde le runloop occupé change tout le temps que ma minuterie va se déclencher?

Vous pouvez configurer des temporisateurs pour générer des événements une seule fois ou à plusieurs reprises. Une minuterie répétitive se replanifie automatiquement en fonction de l'heure de tir planifiée et non de l'heure de tir réelle. Par exemple, si une minuterie est programmée pour se déclencher à une heure particulière et toutes les 5 secondes par la suite, l'heure de tir programmée tombera toujours sur les intervalles de temps d'origine de 5 secondes, même si l'heure de tir réelle est retardée. Si le temps de tir est tellement retardé qu'il manque un ou plusieurs des temps de tir programmés, la minuterie n'est déclenchée qu'une seule fois pour la période de temps manquée. Après avoir tiré pendant la période manquée, la minuterie est reprogrammée pour la prochaine heure de tir prévue.

Comment puis-je changer le mode des RunLoops?

Tu ne peux pas. L'OS se change tout simplement pour vous. par exemple. lorsque l'utilisateur tape, le mode passe à eventTracking. Lorsque les tapotements utilisateur sont terminés, le mode revient à default. Si vous voulez que quelque chose soit exécuté dans un mode spécifique, c'est à vous de vous assurer que cela se produit.


Solution:

Lorsque l'utilisateur fait défiler le mode Run Loop devient tracking . Le RunLoop est conçu pour changer de vitesse. Une fois que le mode est défini sur eventTracking, il donne la priorité (rappelez-vous que nous avons des cœurs de processeur limités) pour toucher les événements. Ceci est une conception architecturale par les concepteurs du système d'exploitation .

Par défaut, les minuteurs ne sont PAS planifiés en mode tracking. Ils sont programmés le:

Crée une minuterie et la planifie sur la boucle d'exécution en cours en mode par défaut .

Le scheduledTimer ci-dessous fait ceci:

RunLoop.main.add(timer, forMode: .default)

Si vous voulez que votre minuterie fonctionne lors du défilement, vous devez faire soit:

let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
 selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode

RunLoop.main.add(timer, forMode: .tracking) // AND Do this

Ou faites simplement:

RunLoop.main.add(timer, forMode: .common)

En fin de compte, l'une des actions ci-dessus signifie que votre thread est pas bloqué par les événements tactiles. ce qui équivaut à:

RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.

Solution alternative:

Vous pouvez envisager d'utiliser GCD pour votre minuterie, ce qui vous aidera à "protéger" votre code des problèmes de gestion de boucle d'exécution.

Pour la non-répétition, utilisez simplement:

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    // your code here
}

Pour répéter les minuteries, utilisez:

Voir comment utiliser DispatchSourceTimer


En creusant plus profondément à partir d'une discussion que j'ai eue avec Daniel Jalkut:

Question: comment GCD (threads d'arrière-plan), par exemple un asyncAfter sur un thread d'arrière-plan s'exécute en dehors de RunLoop? Ma compréhension de cela est que tout doit être exécuté dans un RunLoop

Pas nécessairement - chaque thread a au plus une boucle d'exécution, mais peut avoir zéro s'il n'y a aucune raison de coordonner la "propriété" d'exécution du thread. (édité)

Les threads sont une option au niveau du système d'exploitation qui donne à votre processus la possibilité de fractionner ses fonctionnalités dans plusieurs contextes d'exécution parallèles. Les boucles d'exécution sont une option au niveau du cadre qui vous permet de diviser davantage un seul thread afin qu'il puisse être partagé efficacement par plusieurs chemins de code.

En règle générale, si vous envoyez quelque chose qui s'exécute sur un thread, il n'aura probablement pas de boucle d'exécution sauf si quelque chose appelle [NSRunLoop currentRunLoop] qui en créerait implicitement un.

En bref, les modes sont fondamentalement un mécanisme de filtre pour les entrées et les temporisateurs

0
Honey