Comprendre NSRunLoop

108

Quelqu'un peut-il expliquer ce que c'est NSRunLoop? comme je le sais, NSRunLoopest-ce que quelque chose est lié à NSThreadnon? Supposons donc que je crée un fil comme

NSThread* th=[[NSThread alloc] initWithTarget:self selector:@selector(someMethod) object:nil];
[th start];

-(void) someMethod
{
    NSLog(@"operation");
}

donc après que ce fil ait terminé son travail, non? pourquoi utiliser RunLoopsou où utiliser? à partir de la documentation Apple, j'ai lu quelque chose mais ce n'est pas clair pour moi, alors veuillez expliquer aussi simple que possible

taffarel
la source
Cette question a une portée trop large. Veuillez affiner votre question pour quelque chose de plus spécifique.
Jody Hagins
3
au début, je veux savoir ce que faire dans NSRunLoop générique et comment il est connecté avec Thread
taffarel

Réponses:

211

Une boucle d'exécution est une abstraction qui (entre autres) fournit un mécanisme pour gérer les sources d'entrée du système (sockets, ports, fichiers, clavier, souris, minuteries, etc.).

Chaque NSThread a sa propre boucle d'exécution, accessible via la méthode currentRunLoop.

En général, vous n'avez pas besoin d'accéder directement à la boucle d'exécution, bien que certains composants (réseau) vous permettent de spécifier la boucle d'exécution qu'ils utiliseront pour le traitement d'E / S.

Une boucle d'exécution pour un thread donné attendra qu'une ou plusieurs de ses sources d'entrée aient des données ou un événement, puis déclenchera le ou les gestionnaires d'entrée appropriés pour traiter chaque source d'entrée qui est "prête".

Après cela, il retournera à sa boucle, traitant les entrées provenant de diverses sources, et «dormant» s'il n'y a pas de travail à faire.

C'est une description de niveau assez élevé (en essayant d'éviter trop de détails).

ÉDITER

Une tentative pour répondre au commentaire. Je l'ai cassé en morceaux.

  • cela signifie que je ne peux accéder / exécuter que pour exécuter la boucle à l'intérieur du thread, non?

En effet. NSRunLoop n'est pas thread-safe et ne doit être accessible qu'à partir du contexte du thread qui exécute la boucle.

  • y a-t-il un exemple simple comment ajouter un événement pour exécuter une boucle?

Si vous souhaitez surveiller un port, vous ajouterez simplement ce port à la boucle d'exécution, puis la boucle d'exécution surveillera l'activité de ce port.

- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode

Vous pouvez également ajouter une minuterie explicitement avec

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • que signifie qu'il retournera alors à sa boucle?

La boucle d'exécution traitera tous les événements prêts à chaque itération (selon son mode). Vous devrez consulter la documentation pour découvrir les modes d'exécution, car cela dépasse un peu le cadre d'une réponse générale.

  • la boucle d'exécution est-elle inactive lorsque je démarre le thread?

Dans la plupart des applications, la boucle d'exécution principale s'exécutera automatiquement. Cependant, vous êtes responsable du démarrage de la boucle d'exécution et de la réponse aux événements entrants pour les threads que vous faites tourner.

  • est-il possible d'ajouter des événements à la boucle d'exécution de thread en dehors du thread?

Je ne sais pas ce que vous voulez dire ici. Vous n'ajoutez pas d'événements à la boucle d'exécution. Vous ajoutez des sources d'entrée et des sources de minuterie (à partir du thread qui possède la boucle d'exécution). La boucle de course les surveille ensuite pour l'activité. Vous pouvez, bien sûr, fournir des données d'entrée à partir d'autres threads et processus, mais l'entrée sera traitée par la boucle d'exécution qui surveille ces sources sur le thread qui exécute la boucle d'exécution.

  • cela signifie-t-il que parfois je peux utiliser la boucle d'exécution pour bloquer le thread pendant un certain temps

En effet. En fait, une boucle d'exécution "restera" dans un gestionnaire d'événements jusqu'à ce que ce gestionnaire d'événements soit retourné. Vous pouvez le voir dans n'importe quelle application assez simplement. Installez un gestionnaire pour toute action d'E / S (par exemple, appuyez sur un bouton) qui se met en veille. Vous bloquerez la boucle d'exécution principale (et toute l'interface utilisateur) jusqu'à ce que cette méthode se termine.

La même chose s'applique à toute boucle d'exécution.

Je vous suggère de lire la documentation suivante sur les boucles d'exécution:

https://developer.apple.com/documentation/foundation/nsrunloop

et comment ils sont utilisés dans les threads:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1

Jody Hagins
la source
2
cela signifie que je ne peux accéder / exécuter que pour exécuter la boucle à l'intérieur du thread, non? y a-t-il un exemple simple comment ajouter un événement pour exécuter une boucle? que signifie qu'il retournera alors à sa boucle? la boucle d'exécution est-elle inactive lorsque je démarre le thread? est-il possible d'ajouter des événements à la boucle d'exécution de thread en dehors du thread? cela signifie-t-il que parfois je peux utiliser la boucle d'exécution pour bloquer le thread pendant un certain temps?
taffarel
"Installez un gestionnaire pour toute action IO (par exemple, appuyez sur un bouton) qui se met en veille." Voulez-vous dire que si je continue à maintenir mon doigt sur le bouton, il continuera à bloquer le fil pendant un certain temps?!
Honey
Non. Ce que je veux dire, c'est que le runloop ne traite pas les nouveaux événements tant que le gestionnaire n'est pas terminé. Si vous dormez (ou effectuez une opération qui prend du temps) dans le gestionnaire, la boucle d'exécution se bloquera jusqu'à ce que le gestionnaire ait terminé son travail.
Jody Hagins
@taffarel est-il possible d'ajouter des événements à la boucle d'exécution de thread en dehors du thread? Si cela signifie «puis-je faire exécuter du code sur la boucle d'un autre thread à volonté», alors la réponse est en effet oui. Appelez simplement performSelector:onThread:withObject:waitUntilDone:, en passant un NSThreadobjet et votre sélecteur sera planifié sur la boucle d'exécution de ce thread.
Mecki
12

Les boucles d'exécution sont ce qui sépare les applications interactives des outils de ligne de commande.

  • Les outils de ligne de commande sont lancés avec des paramètres, exécutent leur commande, puis se terminent.
  • Les applications interactives attendent l'entrée de l'utilisateur, réagissent, puis reprennent l'attente.

D' ici

Ils vous permettent d'attendre que l'utilisateur tape et de répondre en conséquence, d'attendre que vous obteniez un completionHandler et d'appliquer ses résultats, d'attendre d'avoir une minuterie et d'exécuter une fonction. Si vous n'avez pas de boucle d'exécution, vous ne pouvez pas écouter / attendre les tapotements des utilisateurs, vous ne pouvez pas attendre qu'un appel réseau se produise, vous ne pouvez pas être réveillé en x minutes à moins d'utiliser DispatchSourceTimerouDispatchWorkItem

Également de ce commentaire :

Les threads d'arrière-plan n'ont pas leurs propres boucles d'exécution, mais vous pouvez simplement en ajouter une. Par exemple, AFNetworking 2.x l'a fait. C'était une technique éprouvée pour NSURLConnection ou NSTimer sur les threads d'arrière-plan, mais nous ne le faisons plus beaucoup nous-mêmes, car les nouvelles API éliminent le besoin de le faire. Mais il semble que URLSession le fasse, par exemple, voici une simple requête , exécutant [voir le panneau de gauche de l'image] des gestionnaires d'achèvement sur la file d'attente principale, et vous pouvez voir qu'il a une boucle d'exécution sur le thread d'arrière-plan


Plus précisément à propos de: "Les threads d'arrière-plan n'ont pas leurs propres boucles d'exécution". Le minuteur suivant ne parvient pas à se déclencher pour une distribution asynchrone :

class T {
    var timer: Timer?

    func fireWithoutAnyQueue() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
            print("without any queue") // success. It's being ran on main thread, since playgrounds begin running from main thread
        })
    }

    func fireFromQueueAsnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — async") // failed to print
            })
        }
    }

    func fireFromQueueSnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.sync {
            timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — sync") // success. Weird. Read my possible explanation below
            })
        }
    }

    func fireFromMain() {
        DispatchQueue.main.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from main queue — sync") //success
            })
        }
    }
}

Je pense que la raison pour laquelle le syncbloc fonctionne également est que:

Les blocs de synchronisation ne sont généralement exécutés qu'à partir de leur file d'attente source . Dans cet exemple, la file d'attente source est la file d'attente principale, la file d'attente quelle qu'elle soit est la file d'attente de destination.

Pour tester cela, je me suis connecté RunLoop.currentà chaque envoi.

La distribution de synchronisation avait la même boucle d'exécution que la file d'attente principale. Alors que le RunLoop dans le bloc async était une instance différente des autres. Vous vous demandez peut-être pourquoi RunLoop.currentrenvoie une valeur différente. N'est-ce pas une valeur partagée !? Excellente question! Lire la suite:

NOTE IMPORTANTE:

La propriété de classe current n'est PAS une variable globale.

Renvoie la boucle d'exécution pour le thread actuel .

C'est contextuel. Il n'est visible que dans la portée du thread, c'est -à- dire le stockage local du thread . Pour en savoir plus, cliquez ici .

Il s'agit d'un problème connu avec les minuteries. Vous n'avez pas le même problème si vous utilisezDispatchSourceTimer

Mon chéri
la source
8

Les RunLoops sont un peu comme une boîte où les choses se passent simplement.

Fondamentalement, dans un RunLoop, vous allez traiter certains événements puis revenir. Ou retournez s'il ne traite aucun événement avant que le délai d'attente ne soit atteint. Vous pouvez le dire comme similaire aux NSURLConnections asynchrones, traiter les données en arrière-plan sans interférer avec votre boucle actuelle et, en même temps, vous avez besoin de données de manière synchrone. Ce qui peut être fait avec l'aide de RunLoop qui rend votre asynchrone NSURLConnectionet fournit des données au moment de l'appel. Vous pouvez utiliser un RunLoop comme ceci:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) {
    loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
}

Dans ce RunLoop, il fonctionnera jusqu'à ce que vous ayez terminé certains de vos autres travaux et que vous définissiez YourBoolFlag sur false .

De même, vous pouvez les utiliser dans les threads.

J'espère que cela vous aide.

Akshay Sunderwani
la source
0

Les boucles d'exécution font partie de l'infrastructure fondamentale associée aux threads. Une boucle d'exécution est une boucle de traitement d'événements que vous utilisez pour planifier le travail et coordonner la réception des événements entrants. Le but d'une boucle d'exécution est de garder votre thread occupé lorsqu'il y a du travail à faire et de le mettre en veille lorsqu'il n'y en a pas.

D'ici


La caractéristique la plus importante de CFRunLoop est les CFRunLoopModes. CFRunLoop fonctionne avec un système de «Run Loop Sources». Les sources sont enregistrées sur une boucle d'exécution pour un ou plusieurs modes, et la boucle d'exécution elle-même est conçue pour s'exécuter dans un mode donné. Lorsqu'un événement arrive sur une source, il n'est géré par la boucle d'exécution que si le mode source correspond au mode courant de la boucle d'exécution.

D'ici

dengApro
la source