30

Я хочу иметь возможность выполнить 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]; 

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

ответ

66

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

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. (Core Animation в настоящее время определяет порядок 2000000, но это не документировано, так что может измениться.)

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

+0

Спасибо за добавление деталей CFRunLoop. – lal

+0

Это отличное резюме того, что делает цикл выполнения. Для вопроса OP я бы сказал, что 'CFRunLoopPerformBlock()' будет самым кошерным способом убедиться, что блок выполняется в следующем цикле цикла выполнения. В документах явно не указано, что блок не будет выполнен, если он добавлен в середине цикла, но они говорят: «Этот метод блокирует только блок и не запускает автоматически указанный цикл цикла, выполнение блока происходит в следующий раз, когда цикл запуска просыпается, чтобы обрабатывать другой источник ввода ». Вы можете проверить, добавив блок в обратный вызов наблюдателя. –

0

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

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

редактировать:

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

+0

Я не уверен, что это правда. В документе указывается: 'Основная очередь отправки - глобально доступная последовательная очередь, которая выполняет задачи в основном потоке приложения. Эта очередь работает с циклом запуска приложения (если таковой имеется) для чередования выполнения задач с очередью с выполнением других источников событий, связанных с циклом выполнения. См. Https://developer.apple.com/library/mac/ # documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html # // apple_ref/doc/uid/TP40008091-CH102-SW2 – lms

-3

dispatch_async on mainQueue - хорошее предложение, но он не запускается в следующем цикле запуска, он вставляется в текущий прогон в цикле.

Чтобы получить поведение вы после вам нужно будет прибегнуть к традиционному способу:

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

Это также дает дополнительное преимущество, это может быть отменен с помощью cancelPreviousPerforms NSObject в.

-3

Я написал себе NSObject category, который принимает значение переменной задержки, основанное на another stackoverflow question. Передавая значение нуля, вы фактически делаете код запущенным на следующей доступной итерации runloop.

Смежные вопросы