it-swarm.com.ru

Как запланировать запуск блока на следующей итерации цикла выполнения?

Я хочу иметь возможность выполнять block на следующей итерации цикла выполнения. Не так важно, выполняется ли это в начале или в конце следующего цикла выполнения, просто это выполнение откладывается до тех пор, пока не завершится выполнение всего кода в текущем цикле выполнения.

Я знаю, что следующее не работает, потому что оно чередуется с основным циклом выполнения, поэтому мой код может выполняться в следующем цикле выполнения, но это может и не выполняться.

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

Следующее, я считаю, страдает той же проблемой, что и выше:

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

Теперь я полагаю следующее будет работать, поскольку оно помещается в конец текущего цикла выполнения (поправьте меня, если я ошибаюсь), будет ли это на самом деле работать?

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

Как насчет таймера с интервалом 0? Документация гласит: If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead. Означает ли это гарантировать выполнение на следующей итерации цикла выполнения?

[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(myMethod) userInfo:nil repeats:NO];

Это все варианты, которые я могу придумать, но я все еще не ближе к выполнению блока (в отличие от вызова метода) на следующей итерации цикла выполнения с гарантией того, что это произойдет не раньше.

34
lms

Вы можете не знать обо всем, что цикл выполнения выполняет в каждой итерации. (Я не был до того, как я исследовал этот ответ!) Как это происходит, CFRunLoop является частью пакета с открытым исходным кодом CoreFoundation , поэтому мы можем взглянуть точно, что это влечет за собой. Цикл выполнения выглядит примерно так:

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

Вы можете видеть, что есть множество способов подключиться к циклу выполнения. Вы можете создать CFRunLoopObserver, который будет вызываться для любой «деятельности», которую вы хотите. Вы можете создать версию 0 CFRunLoopSource и немедленно сообщить об этом. Вы можете создать подключенную пару CFMessagePorts, обернуть ее в версию 1 CFRunLoopSource и отправить ей сообщение. Вы можете создать CFRunLoopTimer. Вы можете поставить в очередь блоки, используя dispatch_get_main_queue или CFRunLoopPerformBlock.

Вам нужно будет решить, какой из этих API использовать, основываясь на том, когда вы планируете блок и когда вам нужно, чтобы он вызывался.

Например, касания обрабатываются в источнике версии 1, но если вы обрабатываете касание путем обновления экрана, это обновление фактически не выполняется, пока транзакция Core Animation не будет зафиксирована, что происходит в наблюдателе kCFRunLoopBeforeWaiting.

Теперь предположим, что вы хотите запланировать блок во время обработки касания, но хотите, чтобы он выполнялся после фиксации транзакции.

Вы можете добавить свой собственный CFRunLoopObserver для действия kCFRunLoopBeforeWaiting, но этот наблюдатель может запускаться до или после наблюдателя Core Animation, в зависимости от того, какой порядок вы указали и какой порядок указывает Core Animation. (Базовая анимация в настоящее время определяет порядок 2000000, но это не задокументировано, поэтому оно может измениться.)

Чтобы убедиться, что ваш блок работает после наблюдателя Core Animation, даже если ваш наблюдатель запускается до наблюдателя Core Animation, не вызывайте блок непосредственно в обратном вызове вашего наблюдателя. Вместо этого используйте dispatch_async в этой точке, чтобы добавить блок в основную очередь. Помещение блока в основную очередь заставит цикл выполнения немедленно выйти из режима ожидания. Он будет запускать любых наблюдателей kCFRunLoopAfterWaiting, а затем опустошит основную очередь, после чего будет запущен ваш блок.

81
rob mayoff

Ответ Роба великолепен и информативен. Я не пытаюсь заменить это.

Просто читая документацию UIView , я нашел:

завершение 

Блок-объект для выполнения при анимации последовательности заканчивается. Этот блок не имеет возвращаемого значения и принимает один логический аргумент, который указывает, действительно ли анимация завершено до вызова обработчика завершения. Если продолжительность анимация равна 0, этот блок выполняется в начале цикл следующего цикла. Этот параметр может быть НЕДЕЙСТВИТЕЛЕН.

Таким простым решением будет:

UIView.animate(withDuration: 0) {
    // anything
}
0
GaétanZ

Я не верю, что существует какой-либо API, который позволит вам гарантировать выполнение кода при следующем повороте цикла событий. Мне также любопытно, почему вам нужна гарантия, что в цикле больше ничего не запущено, в частности главное.

Я также могу подтвердить, что при использовании PerforSelector: withObject: afterDelay действительно используется таймер на основе runloop, и он будет иметь функциональное поведение, аналогичное dispatch_async'ing для dispatch_get_main_queue ().

Правка:

На самом деле, после повторного прочтения вашего вопроса, похоже, что для завершения вам нужен только поворот current runloop. Если это правда, то dispatch_async - это именно то, что вам нужно. Фактически, весь приведенный выше код дает гарантию, что очередь current завершится.

0
Mattie