Grundlegendes zu NSRunLoop

108

Kann jemand erklären, was ist NSRunLoop? Also, wie ich weiß, NSRunLoopist etwas mit NSThreadrichtig verbunden? Nehmen wir also an, ich erstelle einen Thread wie

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

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

Also, nachdem dieser Thread seine Arbeit beendet hat, oder? Warum verwenden RunLoopsoder wo verwenden? Aus Apple-Dokumenten habe ich etwas gelesen, aber es ist mir nicht klar. Erklären Sie dies bitte so einfach wie möglich

Taffarel
quelle
Diese Frage ist zu weit gefasst. Bitte verfeinern Sie Ihre Frage auf etwas Spezifischeres.
Jody Hagins
3
Zuerst möchte ich wissen, was in allgemeinem NSRunLoop zu tun ist und wie es mit Thread
Taffarel verbunden ist.

Antworten:

211

Eine Run-Schleife ist eine Abstraktion, die (unter anderem) einen Mechanismus zur Verarbeitung von Systemeingabequellen (Sockets, Ports, Dateien, Tastatur, Maus, Timer usw.) bietet.

Jeder NSThread verfügt über eine eigene Ausführungsschleife, auf die über die currentRunLoop-Methode zugegriffen werden kann.

Im Allgemeinen müssen Sie nicht direkt auf die Ausführungsschleife zugreifen, obwohl es einige (Netzwerk-) Komponenten gibt, mit denen Sie möglicherweise angeben können, welche Ausführungsschleife für die E / A-Verarbeitung verwendet wird.

Eine Ausführungsschleife für einen bestimmten Thread wartet, bis eine oder mehrere seiner Eingabequellen Daten oder Ereignisse enthalten, und löst dann die entsprechenden Eingabehandler aus, um jede Eingangsquelle zu verarbeiten, die "bereit" ist.

Danach kehrt es zu seiner Schleife zurück, verarbeitet Eingaben aus verschiedenen Quellen und "schläft", wenn keine Arbeit zu erledigen ist.

Das ist eine ziemlich allgemeine Beschreibung (versucht zu viele Details zu vermeiden).

BEARBEITEN

Ein Versuch, den Kommentar anzusprechen. Ich habe es in Stücke gebrochen.

  • es bedeutet, dass ich nur auf die Schleife innerhalb des Threads zugreifen / sie ausführen kann, oder?

Tatsächlich. NSRunLoop ist nicht threadsicher und sollte nur über den Kontext des Threads aufgerufen werden, in dem die Schleife ausgeführt wird.

  • Gibt es ein einfaches Beispiel für das Hinzufügen eines Ereignisses zur Ausführungsschleife?

Wenn Sie einen Port überwachen möchten, fügen Sie diesen Port einfach der Ausführungsschleife hinzu, und die Ausführungsschleife überwacht diesen Port auf Aktivität.

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

Sie können einen Timer auch explizit mit hinzufügen

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • Was bedeutet, dass es dann zu seiner Schleife zurückkehrt?

Die Ausführungsschleife verarbeitet alle Bereitschaftsereignisse bei jeder Iteration (entsprechend ihrem Modus). Sie müssen sich die Dokumentation ansehen, um Informationen zu den Ausführungsmodi zu erhalten, da dies etwas über den Rahmen einer allgemeinen Antwort hinausgeht.

  • Ist die Run-Schleife inaktiv, wenn ich den Thread starte?

In den meisten Anwendungen wird die Hauptlaufschleife automatisch ausgeführt. Sie sind jedoch dafür verantwortlich, die Ausführungsschleife zu starten und auf eingehende Ereignisse für von Ihnen gesponnene Threads zu reagieren.

  • Ist es möglich, einige Ereignisse zur Thread-Run-Schleife außerhalb des Threads hinzuzufügen?

Ich bin mir nicht sicher, was du hier meinst. Sie fügen der Ausführungsschleife keine Ereignisse hinzu. Sie fügen Eingabequellen und Timerquellen hinzu (aus dem Thread, dem die Ausführungsschleife gehört). Die Run-Schleife überwacht sie dann auf Aktivität. Sie können natürlich Daten von anderen Threads und Prozessen bereitstellen, aber die Eingabe wird von der Ausführungsschleife verarbeitet, die diese Quellen in dem Thread überwacht, in dem die Ausführungsschleife ausgeführt wird.

  • Bedeutet das, dass ich manchmal die Run-Schleife verwenden kann, um den Thread für eine Weile zu blockieren?

Tatsächlich. Tatsächlich "bleibt" eine Ausführungsschleife in einem Ereignishandler, bis dieser Ereignishandler zurückgekehrt ist. Sie können dies in jeder App einfach genug sehen. Installieren Sie einen Handler für jede E / A-Aktion (z. B. Tastendruck), die sich im Ruhezustand befindet. Sie blockieren die Hauptlaufschleife (und die gesamte Benutzeroberfläche), bis diese Methode abgeschlossen ist.

Gleiches gilt für jede Run-Schleife.

Ich schlage vor, Sie lesen die folgende Dokumentation zu Run-Loops:

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

und wie sie in Threads verwendet werden:

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

Jody Hagins
quelle
2
es bedeutet, dass ich nur auf die Schleife innerhalb des Threads zugreifen / sie ausführen kann, oder? Gibt es ein einfaches Beispiel für das Hinzufügen eines Ereignisses zur Ausführungsschleife? Was bedeutet, dass es dann zu seiner Schleife zurückkehrt? Ist die Run-Schleife inaktiv, wenn ich den Thread starte? Ist es möglich, einige Ereignisse zur Thread-Run-Schleife außerhalb des Threads hinzuzufügen? Bedeutet das, dass ich manchmal die Run-Schleife verwenden kann, um den Thread für eine Weile zu blockieren?
Taffarel
"Installieren Sie einen Handler für jede E / A-Aktion (z. B. Tastendruck), die sich im Ruhezustand befindet." Meinst du, wenn ich weiterhin meinen Finger auf dem Knopf halte, wird der Thread eine Zeit lang blockiert?!
Honig
Ich meine, dass der Runloop keine neuen Ereignisse verarbeitet, bis der Handler fertig ist. Wenn Sie im Handler schlafen (oder eine Operation ausführen, die lange dauert), wird die Ausführungsschleife blockiert, bis der Handler seine Arbeit abgeschlossen hat.
Jody Hagins
@taffarel Ist es möglich, einige Ereignisse zur Thread-Run-Schleife außerhalb des Threads hinzuzufügen? Wenn dies bedeutet, dass "Code nach Belieben auf dem Runloop eines anderen Threads ausgeführt werden kann", lautet die Antwort in der Tat "Ja". Rufen Sie einfach an performSelector:onThread:withObject:waitUntilDone:, übergeben Sie ein NSThreadObjekt, und Ihre Auswahl wird im Runloop dieses Threads geplant.
Mecki
12

Ausführungsschleifen unterscheiden interaktive Apps von Befehlszeilentools .

  • Befehlszeilentools werden mit Parametern gestartet, führen ihren Befehl aus und beenden ihn.
  • Interaktive Apps warten auf Benutzereingaben, reagieren und setzen das Warten fort.

Von hier aus

Sie ermöglichen es Ihnen, zu warten, bis der Benutzer tippt und entsprechend reagiert, zu warten, bis Sie einen CompletionHandler erhalten, und seine Ergebnisse anzuwenden, zu warten, bis Sie einen Timer erhalten, und eine Funktion auszuführen. Wenn Sie keinen Runloop haben, können Sie nicht auf Benutzertipps warten / warten, Sie können nicht warten, bis ein Netzwerkanruf stattfindet, Sie können nicht in x Minuten geweckt werden, es sei denn, Sie verwenden DispatchSourceTimeroderDispatchWorkItem

Auch aus diesem Kommentar :

Hintergrund-Threads haben keine eigenen Runloops, aber Sie können einfach einen hinzufügen. ZB hat AFNetworking 2.x es geschafft. Es war eine bewährte Technik für NSURLConnection oder NSTimer für Hintergrundthreads, aber wir tun dies nicht mehr viel selbst, da neuere APIs dies nicht mehr erforderlich machen. Es scheint jedoch, dass URLSession eine einfache Anforderung ausführt , bei der [siehe das linke Feld des Bildes] Abschlusshandler in der Hauptwarteschlange ausgeführt werden, und Sie können sehen, dass im Hintergrundthread eine Ausführungsschleife vorhanden ist


Speziell zu: "Hintergrund-Threads haben keine eigenen Runloops". Der folgende Timer kann bei einem asynchronen Versand nicht ausgelöst werden :

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

Ich denke, der Grund, warum der syncBlock auch ausgeführt wird, ist folgender:

Sync - Blöcke in der Regel nur innerhalb ihrer ausgeführt werden soll Quelle Warteschlange. In diesem Beispiel ist die Quellwarteschlange die Hauptwarteschlange, die beliebige Warteschlange die Zielwarteschlange.

Um zu testen, dass ich mich bei RunLoop.currentjedem Versand angemeldet habe .

Der Synchronisierungsversand hatte dieselbe Runloop wie die Hauptwarteschlange. Während der RunLoop innerhalb des asynchronen Blocks eine andere Instanz als die anderen war. Sie überlegen sich vielleicht, warum RunLoop.currentein anderer Wert zurückgegeben wird. Ist es nicht ein gemeinsamer Wert? Gute Frage! Lesen Sie weiter:

WICHTIGE NOTIZ:

Die class -Eigenschaft current ist KEINE globale Variable.

Gibt die Ausführungsschleife für den aktuellen Thread zurück.

Es ist kontextbezogen. Es ist nur im Rahmen des Threads sichtbar, dh im Thread-lokalen Speicher . Mehr dazu hier .

Dies ist ein bekanntes Problem bei Timern. Sie haben nicht das gleiche Problem, wenn Sie verwendenDispatchSourceTimer

Honig
quelle
8

RunLoops sind ein bisschen wie eine Box, in der einfach etwas passiert.

Grundsätzlich verarbeiten Sie in einer RunLoop einige Ereignisse und kehren dann zurück. Oder kehren Sie zurück, wenn keine Ereignisse verarbeitet werden, bevor das Timeout erreicht ist. Sie können sagen, dass es asynchronen NSURLConnections ähnelt. Sie verarbeiten Daten im Hintergrund, ohne Ihre aktuelle Schleife zu stören, und gleichzeitig benötigen Sie Daten synchron. Dies kann mit Hilfe von RunLoop erfolgen, das Sie asynchron macht NSURLConnectionund Daten zum Zeitpunkt des Aufrufs bereitstellt. Sie können einen RunLoop wie folgt verwenden:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

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

In dieser RunLoop wird sie ausgeführt, bis Sie einige Ihrer anderen Arbeiten abgeschlossen und YourBoolFlag auf false gesetzt haben .

Ebenso können Sie sie in Threads verwenden.

Hoffe das hilft dir.

Akshay Sunderwani
quelle
0

Ausführungsschleifen sind Teil der grundlegenden Infrastruktur, die mit Threads verbunden ist. Eine Ausführungsschleife ist eine Ereignisverarbeitungsschleife, mit der Sie die Arbeit planen und den Empfang eingehender Ereignisse koordinieren. Der Zweck einer Ausführungsschleife besteht darin, Ihren Thread zu beschäftigen, wenn Arbeit zu erledigen ist, und Ihren Thread in den Ruhezustand zu versetzen, wenn keine vorhanden ist.

Von hier


Das wichtigste Merkmal von CFRunLoop sind die CFRunLoopModes. CFRunLoop arbeitet mit einem System von "Run Loop Sources". Quellen werden in einer Ausführungsschleife für einen oder mehrere Modi registriert, und die Ausführungsschleife selbst wird in einem bestimmten Modus ausgeführt. Wenn ein Ereignis an einer Quelle eintrifft, wird es nur von der Ausführungsschleife behandelt, wenn der Quellmodus mit dem aktuellen Modus der Ausführungsschleife übereinstimmt.

Von hier

dengApro
quelle