Jak zaplanować blok do uruchomienia w następnej iteracji pętli run?

Chcę być w stanie wykonać block w następnej iteracji pętli run. Nie ma znaczenia, czy zostanie wykonana na początku czy na końcu następnej pętli run, tylko to, że wykonanie zostanie odroczone do czasu zakończenia wykonywania całego kodu w bieżącej pętli run.

Wiem, że poniższy kod nie działa, ponieważ jest przeplatany z główną pętlą run, więc mój kod może być wykonywany na następnej pętli run, ale może nie.

dispatch_async(dispatch_get_main_queue(),^{
    //my code
});

Następujący, moim zdaniem, problem ten sam jak wyżej:

dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void){
    //my code
});

Teraz ja wierzę następująca opcja będzie działać tak, jak jest umieszczona na końcu bieżącej pętli run (popraw mnie, jeśli się mylę), czy to naprawdę zadziała?

[self performSelector:@selector(myMethod) withObject:nil afterDelay:0];
A może timer z interwałem? Dokumentacja stwierdza: If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead. czy przekłada się to na zagwarantowanie wykonania w następnej iteracji pętli run?
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(myMethod) userInfo:nil repeats:NO];

To wszystkie opcje, które mogę wymyślić, ale nadal nie jestem bliżej wykonania bloku (w przeciwieństwie do wywołania metody) na następnym Uruchom iterację pętli z gwarancją, że nie nastąpi to wcześniej.

Author: Honey, 2013-03-01

4 answers

Możesz nie być świadomy wszystkiego, co robi pętla run w każdej iteracji. (Nie byłem, zanim zbadałem tę odpowiedź!) Tak się składa, że CFRunLoop jest częścią open-source CoreFoundation package , więc możemy przyjrzeć się dokładnie, co to oznacza. Pętla run wygląda mniej więcej tak:

while (true) {
    Call kCFRunLoopBeforeTimers observer callbacks;
    Call kCFRunLoopBeforeSources observer callbacks;
    Perform blocks queued by CFRunLoopPerformBlock;
    Call the callback of each version 0 CFRunLoopSource that has been signalled;
    if (any version 0 source callbacks were called) {
        Perform blocks newly queued by CFRunLoopPerformBlock;
    }
    if (I didn't drain the main queue on the last iteration
        AND the main queue has any blocks waiting)
    {
        while (main queue has blocks) {
            perform the next block on the main queue
        }
    } else {
        Call kCFRunLoopBeforeWaiting observer callbacks;
        Wait for a CFRunLoopSource to be signalled
          OR for a timer to fire
          OR for a block to be added to the main queue;
        Call kCFRunLoopAfterWaiting observer callbacks;
        if (the event was a timer) {
            call CFRunLoopTimer callbacks for timers that should have fired by now
        } else if (event was a block arriving on the main queue) {
            while (main queue has blocks) {
                perform the next block on the main queue
            }
        } else {
            look up the version 1 CFRunLoopSource for the event
            if (I found a version 1 source) {
                call the source's callback
            }
        }
    }
    Perform blocks queued by CFRunLoopPerformBlock;
}

Widać, że istnieje wiele sposobów na zaczepienie się do pętli run. Możesz utworzyć CFRunLoopObserver do wywołania dla dowolnych "działań", które chcesz. Możesz utworzyć wersję 0 CFRunLoopSource i natychmiast dać sygnał. Możesz utworzyć połączoną parę CFMessagePorts, owinąć jedną w wersję 1 CFRunLoopSource i wysłać jej wiadomość. Możesz utworzyć CFRunLoopTimer. Możesz ustawić bloki w kolejce używając dispatch_get_main_queue lub CFRunLoopPerformBlock.

Będziesz musiał zdecydować, którego z tych interfejsów API użyć na podstawie tego, kiedy planujesz blok i kiedy chcesz go wywołać.

Na przykład dotknięcia są obsługiwane w źródle wersji 1, ale jeśli obsłużysz dotyk, aktualizując ekran, Ta aktualizacja nie jest w rzeczywistości wykonywane do momentu zaangażowania operacji Core Animation, co dzieje się w obserwatorze kCFRunLoopBeforeWaiting.

Teraz Załóżmy, że chcesz zaplanować blok podczas obsługi dotyku, ale chcesz, aby został on wykonany po zatwierdzeniu transakcji.

Możesz dodać własne CFRunLoopObserver dla aktywności kCFRunLoopBeforeWaiting, ale ten obserwator może działać przed lub za obserwatorem Core Animation, w zależności od określonej kolejności i kolejności Core Animation. (Core Animation obecnie określa zamówienie 2000000, ale nie jest to udokumentowane, więc może się zmienić.)

Aby upewnić się, że blok działa za obserwatorem Core Animation, nawet jeśli twój obserwator działa przed obserwatorem Core Animation, nie wywołaj bloku bezpośrednio w wywołaniu zwrotnym obserwatora. Zamiast tego użyj dispatch_async, aby dodać blok do kolejki głównej. Umieszczenie bloku w kolejce głównej spowoduje, że pętla run natychmiast się obudzi z "wait". Uruchomi każdy kCFRunLoopAfterWaiting obserwator, a następnie opróżni główną kolejkę, w tym czasie uruchomi Twój blok.

 76
Author: rob mayoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-16 15:43:15

Nie wierzę, że istnieje jakiekolwiek API, które pozwoli Ci zagwarantować, że kod zostanie uruchomiony podczas następnej pętli zdarzenia. Jestem również ciekaw, dlaczego potrzebujesz gwarancji, że nic innego nie działa w pętli, w szczególności głównej.

Mogę również potwierdzić, że użycie perforselectora: withObject: afterDelay używa timera opartego na runloop i będzie miało funkcjonalnie podobne zachowanie do dispatch_async ' ing na dispatch_get_main_queue ().

Edit:

Właściwie, po ponownym przeczytaniu twoje pytanie brzmi, jakbyś potrzebował tylko current runloop turn, aby zakończyć. Jeśli to prawda, to dispatch_async jest dokładnie tym, czego potrzebujesz. W rzeczywistości, cały powyższy kod daje gwarancję, że current runloop turn zakończy się.

 0
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-01 16:25:55

Napisałem sobie kategorię NSObject, która akceptuje zmienną delay value, opartą na innym pytaniu stoskoverflow . Przekazując wartość zero, skutecznie sprawiasz, że kod jest uruchamiany na następnej dostępnej iteracji runloopa.

 -3
Author: Grzegorz Adam Hankiewicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:24

Dispatch_async na mainQueue jest dobrą sugestią, ale nie działa na następnej pętli run, jest wstawiany do bieżącego uruchomienia w pętli.

Aby uzyskać zachowanie, którego szukasz, musisz uciekać się do tradycyjnego sposobu:

[self performSelector:@selector(myMethod) withObject:nil afterDelay:0];

Daje to również dodatkową zaletę, że można ją anulować za pomocą NSObject ' s cancelpreviosperforms.

 -3
Author: malhal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-07 00:17:01