Ответ 1
Возможно, вы не знаете обо всем, что работает цикл выполнения на каждой итерации. (Я не был до того, как я исследовал этот ответ!) Так как это происходит, CFRunLoop
является частью open-source пакета 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 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. (Core Animation в настоящее время указывает порядок 2000000, но это не документировано, чтобы оно могло измениться.)
Чтобы убедиться, что ваш блок работает после наблюдателя Core Animation, даже если ваш наблюдатель работает перед наблюдателем Core Animation, не вызывайте блок непосредственно в обратном вызове наблюдателя. Вместо этого используйте dispatch_async
в этой точке, чтобы добавить блок в основную очередь. Помещение блока в основную очередь заставит цикл выполнения просыпаться из своего "ожидания" немедленно. Он будет запускать любые наблюдатели kCFRunLoopAfterWaiting
, а затем он вытолкнет основную очередь, и в это время он запустит ваш блок.