NSDefaultRunLoopMode vs NSRunLoopCommonModes

114

Ilekroć próbuję pobrać duży związany z pliku UIScrollView, MPMapViewlub coś, proces pobierania zostanie zatrzymany, gdy tylko dotknąć ekranu iPhone. Na szczęście niesamowity post na blogu Jörna sugeruje alternatywną opcję, użycie NSRunLoopCommonModesdo połączenia.

To pozwala mi przyjrzeć się szczegółom dwóch trybów, NSDefaultRunLoopMode i NSRunLoopCommonModes, ale dokument Apple nie wyjaśnia uprzejmie, poza tym, że mówi

NSDefaultRunLoopMode

Tryb obsługi źródeł wejściowych innych niż obiekty NSConnection. Jest to najczęściej używany tryb pętli uruchamiania.

NSRunLoopCommonModes

Obiekty dodane do pętli uruchamiania przy użyciu tej wartości jako trybu są monitorowane przez wszystkie tryby pętli uruchamiania, które zostały zadeklarowane jako elementy zestawu „wspólnych” trybów; szczegółowe informacje można znaleźć w opisie CFRunLoopAddCommonMode.

CFRunLoopAddCommonMode

Źródła, liczniki czasu i obserwatory są rejestrowane w jednym lub kilku trybach pętli uruchamiania i działają tylko wtedy, gdy pętla uruchamiania działa w jednym z tych trybów. Typowe tryby to zestaw trybów pracy w pętli, dla których można zdefiniować zestaw źródeł, timerów i obserwatorów, które są wspólne dla tych trybów. Zamiast rejestrować źródło, na przykład w każdym określonym trybie pętli uruchamiania, możesz zarejestrować je jeden raz we wspólnym pseudo-trybie pętli uruchamiania, a zostanie ono automatycznie zarejestrowane w każdym trybie pętli uruchamiania w trybie wspólnym. Podobnie, gdy tryb jest dodawany do zestawu wspólnych trybów, wszelkie źródła, liczniki czasu lub obserwatory już zarejestrowane we wspólnym pseudo-trybie są dodawane do nowo dodanego wspólnego trybu.

Czy ktoś mógłby wyjaśnić te dwie kwestie w ludzkim języku?

Stkim1
źródło

Odpowiedzi:

204

Pętla uruchamiania to mechanizm, który pozwala systemowi budzić uśpione wątki, aby mogły zarządzać zdarzeniami asynchronicznymi. Zwykle podczas uruchamiania wątku (z wyjątkiem głównego wątku) istnieje opcja uruchomienia wątku w pętli uruchamiania lub nie. Jeśli wątek wykonuje jakieś sortowanie lub długotrwałe operacje bez interakcji ze zdarzeniami zewnętrznymi i bez liczników czasu, nie potrzebujesz pętli uruchamiania, ale jeśli wątek musi odpowiadać na zdarzenia przychodzące, powinien być dołączony do pętli uruchamiania, aby obudź wątek, gdy nadejdą nowe wydarzenia. Dzieje się tak w przypadku NSURLConnectionwygenerowanych wątków, które budzą się tylko po zdarzeniach przychodzących (z sieci).

Każdy wątek może być powiązany z wieloma pętlami uruchamiania lub może być powiązany z określoną pętlą uruchamiania, którą można ustawić do pracy w różnych trybach. „Tryb pętli uruchamiania” to konwencja używana przez system operacyjny do ustanawiania reguł określających, kiedy należy dostarczać określone zdarzenia lub zbierać je w celu późniejszego dostarczenia.

Zwykle wszystkie pętle uruchamiania są ustawione na „tryb domyślny”, który ustanawia domyślny sposób zarządzania zdarzeniami wejściowymi. Na przykład: gdy tylko nastąpi przeciągnięcie myszą (Mac OS) lub dotknięcie (w iOS), tryb dla tej pętli uruchamiania jest ustawiony na śledzenie zdarzeń; oznacza to, że wątek nie zostanie obudzony przy nowych zdarzeniach sieciowych, ale te zdarzenia zostaną dostarczone później, gdy zdarzenie wejściowe użytkownika zakończy się, a pętla uruchamiania zostanie ponownie ustawiona w trybie domyślnym; oczywiście jest to wybór dokonany przez architektów systemu operacyjnego, aby dać priorytet zdarzeniom użytkownika zamiast zdarzeniom w tle.

Jeśli zdecydujesz się zmienić tryb pętli uruchamiania dla swojego NSURLConnectionwątku, używając scheduleInRunLoop:forModes:, możesz przypisać wątek do specjalnego trybu pętli uruchamiania zamiast określonej domyślnej pętli uruchamiania. Wywoływany specjalny pseudo-tryb NSRunLoopCommonModesjest używany przez wiele źródeł wejściowych, w tym śledzenie zdarzeń. Na przykład przypisanie NSURLConnectioninstancji do trybu wspólnego oznacza skojarzenie jej zdarzeń z „trybem śledzenia” oprócz „trybu domyślnego”. Jedną zaletą / wadą powiązania wątków z NSRunLoopCommonModesjest to, że wątek nie będzie blokowany przez zdarzenia dotykowe.

Nowe tryby można dodać do zwykłych trybów, ale jest to operacja dość niskiego poziomu.

Chciałbym zakończyć dodając kilka uwag:

  • Zwykle musimy użyć zestawu obrazów lub miniatur pobranych z sieci z widokiem tabeli. Możemy pomyśleć, że pobranie tych obrazów z sieci podczas przewijania widoku tabeli może poprawić komfort użytkowania (ponieważ mogliśmy oglądać obrazy podczas przewijania), ale nie jest to korzystne, ponieważ płynność przewijania może znacznie ucierpieć. W tym przykładzie z NSURLConnectionpętlą uruchamiania nie należy używać; lepiej byłoby użyć UIScrollViewmetod delegata, aby wykryć zakończenie przewijania, a następnie zaktualizować tabelę i pobrać nowe elementy z sieci;

  • Możesz rozważyć użycie GCD, który pomoże Ci „chronić” kod przed problemami z zarządzaniem pętlą uruchamiania. W powyższym przykładzie możesz rozważyć dodanie żądań sieciowych do niestandardowej kolejki szeregowej.

viggio24
źródło
9
Drogi Viggio24, bardzo dziękuję za to jasne, precyzyjne wyjaśnienie. Poprosiłbym Apple o dołączenie komentarza do ich przewodnika API. ;)
Stkim1
7
Odpowiedź viggio24 jest doskonała. Dla zainteresowanych chciałbym zwrócić uwagę, że sesja 208 (Aplikacje sieciowe na iPhone OS, część 2) z WWDC 2010 zawiera wprowadzenie do pętli uruchamiania. Jeśli jesteś zainteresowany, zajrzyj. Mam nadzieję, że to pomoże.
Lorenzo B
19
Uwaga dla mnie: NSRunLoopCommonModeszezwala na zdarzenie timera podczas przewijania UIScrollView. NSDefaultRunLoopModezapobiegaj licznikowi czasu podczas przewijania.
eonil
2
Bardzo interesujący wydał mi się komentarz dotyczący aktualizacji widoku przewijania, ponieważ wspomina o bardzo trudnym temacie. Aby dodać więcej szczegółów na ten temat: Gdy ustawisz tryb dla NSURLConnection, ma to wpływ tylko na wykonywanie wywołań zwrotnych delegatów. Rozumiem, że aktualizacja scrollView w tym miejscu może spowodować problem z wydajnością, ale dlaczego tak się dzieje? Jeśli odpowiedź brzmi, że obraz musi zostać załadowany do pamięci, możesz to zrobić, wpisując kontekst graficzny w tle i po wykonaniu tej czynności zaktualizuj główny wątek widoku. czy to brzmi rozsądnie?
nebillo,