Zrozumieć NSRunLoop

109

Czy ktoś może wyjaśnić, co to jest NSRunLoop? więc jak wiem, NSRunLoopjest to coś związanego, NSThreadprawda? Więc załóżmy, że tworzę wątek podobny do

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

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

więc po tym, jak ten wątek zakończy pracę, prawda? dlaczego używać RunLoopslub gdzie używać? z dokumentów Apple Coś przeczytałem, ale nie jest to dla mnie jasne, więc proszę wyjaśnij tak prosto, jak to tylko możliwe

taffarel
źródło
To pytanie ma zbyt szeroki zakres. Zawęź pytanie do czegoś bardziej szczegółowego.
Jody Hagins
3
na początku chcę wiedzieć, co robię w genereal NSRunLoop i jak to się łączy z Thread
taffarel

Odpowiedzi:

211

Pętla uruchamiania to abstrakcja, która (między innymi) zapewnia mechanizm do obsługi systemowych źródeł wejściowych (gniazd, portów, plików, klawiatury, myszy, zegarów itp.).

Każdy NSThread ma własną pętlę uruchamiania, do której można uzyskać dostęp za pomocą metody currentRunLoop.

Generalnie nie ma potrzeby bezpośredniego dostępu do pętli uruchamiania, chociaż istnieją pewne komponenty (sieciowe), które mogą pozwolić na określenie, której pętli będą używać do przetwarzania we / wy.

Pętla uruchamiania dla danego wątku będzie czekać, aż jedno lub więcej jego źródeł wejściowych będzie miało jakieś dane lub zdarzenia, a następnie uruchomi odpowiednią procedurę obsługi wejścia w celu przetworzenia każdego źródła wejściowego, które jest „gotowe”.

Po wykonaniu tej czynności powróci do swojej pętli, przetwarzając dane wejściowe z różnych źródeł i „śpiąc”, jeśli nie ma pracy.

To opis na dość wysokim poziomie (próba uniknięcia zbyt wielu szczegółów).

EDYTOWAĆ

Próba odniesienia się do komentarza. Rozbiłam to na kawałki.

  • oznacza to, że mogę uzyskać dostęp / uruchomić tylko pętlę wewnątrz wątku, prawda?

W rzeczy samej. NSRunLoop nie jest bezpieczny dla wątków i powinien być dostępny tylko z kontekstu wątku, który uruchamia pętlę.

  • czy jest jakiś prosty przykład, jak dodać zdarzenie do uruchomienia pętli?

Jeśli chcesz monitorować port, po prostu dodaj ten port do pętli uruchamiania, a następnie pętla uruchamiania będzie obserwować ten port pod kątem aktywności.

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

Możesz również jawnie dodać licznik czasu za pomocą

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • co oznacza, że ​​wróci do swojej pętli?

Pętla uruchamiania będzie przetwarzać wszystkie gotowe zdarzenia w każdej iteracji (zgodnie ze swoim trybem). Będziesz musiał przejrzeć dokumentację, aby dowiedzieć się o trybach uruchamiania, ponieważ jest to nieco poza zakresem ogólnej odpowiedzi.

  • czy pętla uruchamiania jest nieaktywna, kiedy uruchamiam wątek?

W większości aplikacji główna pętla uruchamiania będzie działać automatycznie. Jednak jesteś odpowiedzialny za uruchomienie pętli uruchamiania i reagowanie na zdarzenia przychodzące dla wątków, które obracasz.

  • czy możliwe jest dodanie niektórych zdarzeń do pętli uruchamiania wątku poza wątkiem?

Nie jestem pewien, co masz na myśli. Nie dodajesz zdarzeń do pętli uruchamiania. Dodajesz źródła wejściowe i źródła licznika czasu (z wątku, który jest właścicielem pętli uruchamiania). Następnie pętla biegania obserwuje je pod kątem aktywności. Możesz oczywiście podać dane wejściowe z innych wątków i procesów, ale dane wejściowe będą przetwarzane przez pętlę uruchamiania, która monitoruje te źródła w wątku, który uruchamia pętlę uruchamiania.

  • Czy to oznacza, że ​​czasami mogę użyć pętli uruchamiania do blokowania wątku na jakiś czas

W rzeczy samej. W rzeczywistości pętla uruchamiania „pozostanie” w procedurze obsługi zdarzeń, dopóki ta procedura obsługi zdarzenia nie zwróci. Możesz to zobaczyć w dowolnej aplikacji po prostu. Zainstaluj program obsługi dla każdej akcji we / wy (np. Naciśnięcie przycisku), która jest w stanie uśpienia. Będziesz blokować główną pętlę uruchamiania (i cały interfejs użytkownika) do czasu zakończenia tej metody.

To samo dotyczy każdej pętli uruchamiania.

Proponuję przeczytać następującą dokumentację dotyczącą pętli uruchamiania:

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

i jak są używane w wątkach:

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

Jody Hagins
źródło
2
oznacza to, że mogę uzyskać dostęp / uruchomić tylko pętlę wewnątrz wątku, prawda? czy jest jakiś prosty przykład, jak dodać zdarzenie do uruchomienia pętli? co oznacza, że ​​wróci do swojej pętli? czy pętla uruchamiania jest nieaktywna, kiedy uruchamiam wątek? czy możliwe jest dodanie niektórych zdarzeń do pętli uruchamiania wątku poza wątkiem? czy to oznacza, że ​​czasami mogę użyć pętli uruchamiania do blokowania wątku na jakiś czas?
taffarel
„Zainstaluj program obsługi dla każdej akcji we / wy (np. Naciśnięcie przycisku), która jest w stanie uśpienia”. Czy masz na myśli to, że jeśli nadal będę trzymać palec na przycisku, będzie on nadal blokował wątek przez jakiś czas ?!
Miód
Nie. Chodzi mi o to, że runloop nie przetwarza nowych zdarzeń, dopóki program obsługi nie zakończy działania. Jeśli śpisz (lub wykonujesz jakąś operację, która zajmuje dużo czasu) w module obsługi, pętla uruchamiania będzie blokować się, dopóki procedura obsługi nie zakończy swojej pracy.
Jody Hagins
@taffarel Czy można dodać niektóre zdarzenia do pętli uruchamiania wątku poza wątkiem? Jeśli to oznacza „czy mogę dowolnie uruchamiać kod w pętli uruchomieniowej innego wątku”, to odpowiedź rzeczywiście brzmi tak. Po prostu zadzwoń performSelector:onThread:withObject:waitUntilDone:, przekazując NSThreadobiekt, a twój selektor zostanie zaplanowany w pętli uruchomieniowej tego wątku.
Mecki
12

Pętle uruchamiania oddzielają aplikacje interaktywne od narzędzi wiersza polecenia.

  • Narzędzia wiersza poleceń są uruchamiane z parametrami, wykonują swoje polecenie, a następnie kończą pracę.
  • Aplikacje interaktywne czekają na wejście użytkownika, reagują, a następnie wznawiają oczekiwanie.

od tutaj

Pozwalają one czekać, aż użytkownik naciśnie klawisz i odpowiednio zareagować, poczekać, aż pojawi się completeHandler i zastosować jego wyniki, poczekać, aż pojawi się licznik czasu i wykonać funkcję. Jeśli nie masz pętli, nie możesz słuchać / czekać na stuknięcia użytkownika, nie możesz czekać, aż rozpocznie się połączenie sieciowe, nie możesz zostać obudzony w ciągu x minut, chyba że użyjesz DispatchSourceTimerlubDispatchWorkItem

Również z tego komentarza :

Wątki w tle nie mają własnych pętli uruchomieniowych, ale możesz po prostu dodać jedną. Np. AFNetworking 2.x to zrobił. To była wypróbowana i prawdziwa technika dla NSURLConnection lub NSTimer w wątkach w tle, ale sami już tego nie robimy, ponieważ nowsze interfejsy API eliminują taką potrzebę. Ale wygląda na to, że URLSession robi, np. Tutaj jest proste żądanie , uruchamiające [patrz lewy panel obrazu] procedury obsługi zakończenia w głównej kolejce i widać, że ma pętlę uruchamiania w wątku w tle


W szczególności o: „Wątki w tle nie mają własnych pętli uruchomieniowych”. Następujący licznik czasu nie uruchamia się dla wysłania asynchronicznego :

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

Myślę, że powodem, dla którego syncblok również działa, jest to, że:

syncbloki zwykle wykonywane z kolejki źródłowej . W tym przykładzie kolejka źródłowa jest kolejką główną, niezależnie od kolejki jest kolejką docelową.

Aby to sprawdzić, logowałem się RunLoop.currentdo każdej wysyłki.

Wysłanie synchronizacji miało ten sam runloop co główna kolejka. Podczas gdy RunLoop w bloku async był inną instancją niż pozostałe. Możesz się zastanawiać, dlaczego tak się dziejeRunLoop.current zwraca inną wartość. Czy to nie wspólna wartość !? Świetne pytanie! Czytaj dalej:

WAŻNA UWAGA:

Właściwość klasy current NIE jest zmienną globalną.

Zwraca pętlę uruchamiania dla prądu wątku.

Jest kontekstowe. Jest to widoczne tylko w zakresie wątku, czyli magazynu lokalnego wątku . Więcej na ten temat znajdziesz w tutaj .

Jest to znany problem z licznikami czasu. Nie masz tego samego problemu, jeśli używaszDispatchSourceTimer

kochanie
źródło
8

RunLoops to trochę jak pudełko, w którym coś się po prostu dzieje.

Zasadniczo w RunLoop przechodzisz do przetwarzania niektórych zdarzeń, a następnie wracasz. Lub wróć, jeśli nie przetwarza żadnych zdarzeń przed przekroczeniem limitu czasu. Możesz powiedzieć, że jest to podobne do asynchronicznych połączeń NSURLConnections, Przetwarzanie danych w tle bez zakłócania pętli prądowej, ale jednocześnie potrzebujesz danych synchronicznie. Można to zrobić za pomocą RunLoop, który sprawia, że ​​Twoje jest asynchroniczne NSURLConnectioni dostarcza dane w czasie wywołania. Możesz użyć RunLoop w następujący sposób:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

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

W tym RunLoop będzie działać, dopóki nie wykonasz innej pracy i nie ustawisz YourBoolFlag na false .

Podobnie możesz ich używać w wątkach.

Mam nadzieję, że to ci pomoże.

Akshay Sunderwani
źródło
0

Pętle uruchamiania są częścią podstawowej infrastruktury związanej z wątkami. Pętla uruchamiania to pętla przetwarzania zdarzeń używana do planowania pracy i koordynowania odbioru nadchodzących zdarzeń. Celem pętli uruchamiania jest zajęcie wątku, gdy jest praca do wykonania, i uśpienie wątku, gdy go nie ma.

Stąd


Najważniejszą cechą CFRunLoop jest CFRunLoopModes. CFRunLoop współpracuje z systemem „Run Loop Sources”. Źródła są rejestrowane w pętli uruchamiania dla jednego lub kilku trybów, a sama pętla uruchamiania jest uruchamiana w danym trybie. Gdy zdarzenie pojawia się w źródle, jest obsługiwane przez pętlę uruchamiania tylko wtedy, gdy tryb źródłowy jest zgodny z trybem bieżącym pętli uruchamiania.

Stąd

dengApro
źródło