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

各繰り返しで実行ループが実行するすべてのことを認識していない可能性があります。 (私はこの答えを調べる前ではありませんでした!)起こると、CFRunLoopopen-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のソースで処理されますが、画面を更新してタッチを処理する場合は、kCFRunLoopBeforeWaitingオブザーバで発生するCore Animationトランザクションがコミットされるまで、その更新は実際には実行されません。

ここで、タッチを処理している間にブロックをスケジュールするとしますが、トランザクションがコミットされた後にブロックを実行するとします。

kCFRunLoopBeforeWaitingアクティビティに独自のCFRunLoopObserverを追加できますが、このオブザーバは、指定した順序とCore Animationで指定された順序に応じて、Core Animationのオブザーバの前後に実行される場合があります。 (Core Animationのは、現在、2000000の順序を指定しますが、それは変更することができるように。それが文書化されていない)

をCore Animationののオブザーバー後に確認してくださいあなたのブロックの実行を作るためにあなたの観察者が前のCore Animationのオブザーバーを実行されている場合でも、ドン」あなたのオブザーバーのコールバックでブロックを直接呼び出してください。代わりに、その時点でdispatch_asyncを使用してブロックをメインキューに追加します。メインキューにブロックを置くと、実行ループはすぐに "待機"から起きるよう強制されます。任意のkCFRunLoopAfterWaitingオブザーバが実行され、メインキューが排除されます。その時点でブロックが実行されます。

+0

CFRunLoopの詳細を追加していただきありがとうございます。 – lal

+0

これは、実行ループが行うことの素晴らしい要約です。 OPの質問については、 'CFRunLoopPerformBlock()'がブロックが実行ループの次のループで実行されることを確認する最も賢明な方法であると言いたいと思います。ブロックがループの途中で追加された場合、ブロックが実行されないことを明示的に述べていませんが、「このメソッドはブロックのみをエンキューし、指定された実行ループを自動的に起動させません。ブロックの実行は、次に実行ループが別の入力ソースを処理するために起動したときに発生します。オブザーバーのコールバックにブロックを追加することでチェックできます。 –

0

非常に次のイベントループターンでコードが実行されることを保証するAPIはないと思います。私はまた、ループの中で他に何も実行していないという保証が必要なのも興味深いです。

また、perforSelector:withObject:afterDelayを使用すると、runloopベースのタイマーを使用し、dispatch_get_main_queue()でdispatch_asyncと似た動作をすることが確認できます。

編集:あなただけ現在が完了するまでに回し実行ループの必要があるよう

は実際には、再読み込み、あなたの質問の後、それが聞こえます。それが本当であれば、dispatch_asyncは必要なものです。実際には、上記のすべてのコードは、現在 runloopターンが完了するという保証をしています。

+0

私はそれが本当であるかどうかはわかりません。主なディスパッチキューは、アプリケーションのメインスレッド上でタスクを実行する、グローバルに利用可能なシリアルキューです。このキューは、実行されているタスクの実行と、実行ループにアタッチされた他のイベントソースの実行をインターリーブするアプリケーションの実行ループ(存在する場合)と連携します.' https://developer.apple.com/library/mac/ #document/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#// apple_ref/doc/uid/TP40008091-CH102-SW2 – lms

-3

mainQueueのdispatch_asyncは良い提案ですが、ループ内の現在の実行に挿入される次の実行ループでは実行されません。あなたは伝統的な方法に頼る必要があります後、あなたが行動を取得するには

:これはまた、追加の利点を提供します

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

は、それがNSObjectののcancelPreviousPerformsを使用してキャンセルすることができます。

-3

私は自分自身で、another stackoverflow questionに基づいて可変遅延値を受け入れるNSObject categoryを書きました。値0を渡すことで、次の利用可能なループループ反復でコードを効果的に実行させることができます。

関連する問題