46

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

これが私が考えられるすべてのオプションですが、次の実行ループ反復で (メソッドを呼び出すのではなく) ブロックを実行することにはまだ近づいていません。

4

5 に答える 5

103

実行ループが各反復で行うすべてのことを認識していない場合があります。(この回答を調査する前は知りませんでした!)たまたま、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すぐに通知できます。の接続されたペアを作成し、CFMessagePorts1 つをバージョン 1 でラップしCFRunLoopSourceて、メッセージを送信できます。を作成できますCFRunLoopTimerdispatch_get_main_queueまたはを使用して、ブロックをキューに入れることができますCFRunLoopPerformBlock

ブロックをいつスケジュールするか、いつ呼び出す必要があるかに基づいて、これらの API のどれを使用するかを決定する必要があります。

たとえば、タッチはバージョン 1 のソースで処理されますが、画面を更新してタッチを処理する場合、kCFRunLoopBeforeWaitingオブザーバーで発生する Core Animation トランザクションがコミットされるまで、その更新は実際には実行されません。

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

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

ブロックがコア アニメーションのオブザーバーの後に実行されるようにするには、オブザーバーがコア アニメーションのオブザーバーの前に実行される場合でも、オブザーバーのコールバックでブロックを直接呼び出さないでください。代わりに、dispatch_asyncその時点で を使用してブロックをメイン キューに追加します。ブロックをメイン キューに置くと、実行ループが強制的に「待機」からすぐに復帰します。kCFRunLoopAfterWaitingオブザーバーを実行してからメイン キューを空にし、その時点でブロックを実行します。

于 2013-03-01T23:16:49.223 に答える
0

次のイベント ループ ターンでコードが実行されることを保証できる API があるとは思えません。また、ループ、特にメインのループで他に何も実行されていないという保証が必要な理由にも興味があります。

また、perforSelector:withObject:afterDelay を使用すると、runloop ベースのタイマーが使用され、dispatch_get_main_queue() での dispatch_async と機能的に同様の動作になることも確認できます。

編集:

実際、あなたの質問を読み直した後、完了するには現在のランループターンだけが必要なようです。それが本当なら、dispatch_async はまさにあなたが必要としているものです。実際、上記のコードはすべて、現在の実行ループのターンが完了することを保証しています。

于 2013-03-01T16:25:55.937 に答える
-3

別の stackoverflow questionに基づいて、可変遅延値を受け入れるNSObject カテゴリを自分で作成しました。ゼロの値を渡すことで、次に利用可能な実行ループの反復でコードを効果的に実行できます。

于 2013-05-31T08:32:47.250 に答える
-3

mainQueue の dispatch_async は良い提案ですが、ループ内の現在の実行に挿入される次の実行ループでは実行されません。

求めている動作を得るには、従来の方法に頼る必要があります。

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

これには、NSObject の cancelPreviousPerforms を使用してキャンセルできるという追加の利点もあります。

于 2013-05-31T08:07:17.787 に答える