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?
ios
uiscrollview
nstimer
mcccclean
źródło
źródło
Odpowiedzi:
Łatwe i proste do wdrożenia rozwiązanie to:
NSTimer *timer = [NSTimer timerWithTimeInterval:... target:... selector:.... userInfo:... repeats:...]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
źródło
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)
źródło
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
jako pierwsze polecenie zamiastTimer.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.Tak, Paul ma rację, to kwestia pętli uruchamiania. W szczególności musisz skorzystać z metody NSRunLoop:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
źródło
To jest szybka wersja.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true) NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
źródło
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.
źródło
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)
źródło
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 .
Co to jest Run Loop?
W jaki sposób zakłócana jest realizacja wydarzeń?
Co się stanie, jeśli licznik czasu zostanie uruchomiony, gdy pętla uruchamiania jest w środku wykonywania?
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
Czy przewijanie lub cokolwiek, co sprawia, że pętla jest zajęta, zmienia się za każdym razem, gdy mój licznik czasu się uruchomi?
Jak mogę zmienić tryb RunLoops?
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 dodefault
. Jeśli chcesz, aby coś działało w określonym trybie, to od Ciebie zależy, czy tak się stanie.Rozwiązanie:
Kiedy użytkownik przewija, tryb Run Loop zmienia się na
tracking
. RunLoop jest przeznaczony do zmiany biegów. Gdy tryb jest ustawiony naeventTracking
, 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: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.
Alternatywne rozwiązanie:
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
źródło