Podczas UIScrollView
przewijania (lub jej klasy pochodnej), wygląda na to, że wszystkie NSTimers
uruchomione programy zostają wstrzymane do czasu zakończenia przewijania.
Czy można to obejść? Wątki? Ustawienie priorytetu? Byle co?
Podczas UIScrollView
przewijania (lub jej klasy pochodnej), wygląda na to, że wszystkie NSTimers
uruchomione programy zostają wstrzymane do czasu zakończenia przewijania.
Czy można to obejść? Wątki? Ustawienie priorytetu? Byle co?
Odpowiedzi:
Łatwe i proste do wdrożenia rozwiązanie to:
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Dla każdego, kto używa Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
jako pierwsze polecenie zamiast Timer.scheduleTimer()
, ponieważ scheduleTimer()
dodaje licznik czasu do runloop, a następne wywołanie jest kolejnym dodaniem do tego samego runloop, ale z innym trybem. Nie wykonuj dwukrotnie tej samej pracy.
To jest szybka wersja.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
Musisz uruchomić inny wątek i kolejną pętlę uruchamiania, jeśli chcesz, aby liczniki czasu były uruchamiane podczas przewijania; Ponieważ liczniki są przetwarzane jako część pętli zdarzeń, jeśli jesteś zajęty przewijaniem widoku, nigdy nie możesz przejść do liczników. Chociaż kara za wydajność / baterię w przypadku uruchamiania timerów w innych wątkach może nie być warta zajęcia się tym przypadkiem.
dla każdego używaj Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
tl; dr runloop wykonuje przewijanie, więc nie może obsłużyć więcej zdarzeń - chyba że ręcznie ustawisz licznik czasu, aby mogło się to również zdarzyć, gdy runloop obsługuje zdarzenia dotykowe. Lub wypróbuj alternatywne rozwiązanie i użyj GCD
Lektura obowiązkowa dla każdego programisty iOS. Wiele rzeczy jest ostatecznie wykonywanych przez RunLoop.
Pochodzi z dokumentów Apple .
Pętla biegu jest bardzo podobna do jej nazwy. Jest to pętla, do której wchodzi wątek i której używa do uruchamiania programów obsługi zdarzeń w odpowiedzi na nadchodzące zdarzenia
Ponieważ liczniki czasu i inne zdarzenia okresowe są dostarczane po uruchomieniu pętli uruchamiania, obejście tej pętli zakłóca dostarczanie tych zdarzeń. Typowy przykład takiego zachowania występuje zawsze, gdy implementujesz procedurę śledzenia myszy, wprowadzając pętlę i wielokrotnie żądając zdarzeń z aplikacji. Ponieważ kod przechwytuje zdarzenia bezpośrednio, a nie pozwala aplikacji normalnie wywoływać te zdarzenia, aktywne zegary nie będą mogły zostać uruchomione, dopóki procedura śledzenia myszy nie zakończy się i nie zwróci kontroli do aplikacji.
Dzieje się to DUŻO RAZ, a my nigdy tego nie zauważamy. Mam na myśli, że ustawiliśmy zegar na uruchomienie o 10:00, ale runloop wykonuje zdarzenie, które trwa do 10:10:10:05, dlatego zegar jest uruchamiany 10: 10: 10: 06
Podobnie, jeśli licznik czasu uruchamia się, gdy pętla uruchamiania jest w trakcie wykonywania procedury obsługi, licznik czasu czeka do następnego przejścia przez pętlę wykonywania, aby wywołać procedurę obsługi. Jeśli pętla w ogóle nie działa, licznik czasu nigdy nie zostanie uruchomiony.
Możesz skonfigurować timery tak, aby generowały zdarzenia tylko raz lub wielokrotnie. Czasomierz powtarzania samoczynnie zmienia harmonogram na podstawie zaplanowanego czasu odpalania, a nie faktycznego czasu odpalania. Na przykład, jeśli zegar ma odpalić w określonym czasie i co 5 sekund po tym, zaplanowany czas strzelania będzie zawsze przypadał na pierwotne 5-sekundowe interwały, nawet jeśli rzeczywisty czas odpalania zostanie opóźniony. Jeśli czas strzelania jest tak bardzo opóźniony, że omija jeden lub więcej zaplanowanych czasów strzelania, licznik czasu jest uruchamiany tylko raz na brakujący czas. Po strzale za nieudany okres, licznik czasu zostaje przesunięty na następny zaplanowany czas strzelania.
Nie możesz. System operacyjny po prostu zmienia się dla Ciebie. np. gdy użytkownik puknie, tryb przełącza się na eventTracking
. Po zakończeniu stuknięć przez użytkownika tryb powraca do default
. Jeśli chcesz, aby coś działało w określonym trybie, to od Ciebie zależy, czy tak się stanie.
Kiedy użytkownik przewija, tryb Run Loop zmienia się na tracking
. RunLoop jest przeznaczony do zmiany biegów. Gdy tryb jest ustawiony na eventTracking
, daje priorytet (pamiętaj, że mamy ograniczone rdzenie procesora), aby zdarzenia dotknąć. To jest projekt architektoniczny wykonany przez projektantów systemu operacyjnego .
Domyślnie timery NIE są zaplanowane w tym tracking
trybie. Są zaplanowane na:
Tworzy licznik czasu i planuje go w bieżącej pętli uruchamiania w trybie domyślnym .
Pod scheduledTimer
spodem robi to:
RunLoop.main.add(timer, forMode: .default)
Jeśli chcesz, aby zegar działał podczas przewijania, musisz wykonać jedną z poniższych czynności:
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
Lub po prostu zrób:
RunLoop.main.add(timer, forMode: .common)
Ostatecznie wykonanie jednego z powyższych oznacza, że Twój wątek nie jest blokowany przez zdarzenia dotykowe. co jest równoważne z:
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.
Możesz rozważyć użycie GCD jako timera, który pomoże ci "chronić" kod przed problemami z zarządzaniem pętlą uruchamiania.
Aby nie powtarzać, po prostu użyj:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// your code here
}
Do powtarzających się timerów użyj:
Zobacz, jak używać DispatchSourceTimer
Sięgając głębiej do dyskusji, którą odbyłem z Danielem Jalkutem:
Pytanie: w jaki sposób GCD (wątki w tle), np. AsyncAfter w wątku w tle, są wykonywane poza RunLoop? Z tego rozumiem, że wszystko ma być wykonane w ramach RunLoop
Niekoniecznie - każdy wątek ma co najwyżej jedną pętlę uruchamiania, ale może mieć zero, jeśli nie ma powodu, aby koordynować wykonanie „własności” wątku.
Wątki to afordancja na poziomie systemu operacyjnego, która umożliwia procesowi dzielenie jego funkcjonalności na wiele równoległych kontekstów wykonywania. Pętle uruchamiania to afordancja na poziomie struktury, która umożliwia dalsze dzielenie pojedynczego wątku, aby można go było wydajnie udostępniać przez wiele ścieżek kodu.
Zwykle, jeśli wysyłasz coś, co jest uruchamiane w wątku, prawdopodobnie nie będzie miało pętli rozruchowej, chyba że wywoła coś, [NSRunLoop currentRunLoop]
co niejawnie je utworzy.
Krótko mówiąc, tryby są w zasadzie mechanizmem filtrującym wejścia i timery