web-dev-qa-db-ja.com

performBlock:とperformBlockAndWait :?の動作の違い

ファイルやサービスから取得したデータの更新を処理するために、プライベートキューにNSManagedObjectContextを作成しています。

NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;

私はプライベートキューを使用しているため、performBlock:メソッドとperformBlockAndWait:メソッドの違いを完全には理解していません...データ更新を実行するには、現在次のようにしています:

[privateContext performBlock: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];

        dispatch_async(dispatch_get_main_queue(), ^{
            // Notify update to user
        });
    }];

この場合、データの更新は同期して順次行われるので、コンテキストを保存するための正しい場所だと思いますよね?私が何か間違ったことをしている場合は、ご連絡いただければ幸いです。一方、このコードは同等ですか?:

[privateContext performBlockAndWait: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];
    }];

// Notify update to user

繰り返しますが、これはコンテキストを保存するための正しい場所だと思います...両方の方法の違いは何ですか(この場合、ある場合)?

同期サービス呼び出しやファイル解析を実行する代わりに、非同期サービス呼び出しを実行する必要がある場合はどうなりますか?これらのデータ更新はどのように管理されますか?

前もって感謝します

26
AppsDev

MOCで実行したいことはすべてperformBlockまたはperformBlockAndWait内で行う必要があるという点で正しいです。保持/解放は管理対象オブジェクトに対してスレッドセーフであるため、管理対象オブジェクトの参照カウントを保持/解放するためにこれらのブロックの1つにいる必要はありません。

どちらも同期キューを使用してメッセージを処理します。つまり、一度に実行されるブロックは1つだけです。まあ、それはほとんど本当です。 performBlockAndWaitの説明を参照してください。いずれの場合でも、MOCへのアクセスはシリアル化され、一度に1つのスレッドのみがMOCにアクセスします。

tl; dr違いを気にせず、常にperformBlockを使用してください。

事実上の違い

いくつかの違いがあります。まだまだあると思いますが、ここで私が理解しておくべき最も重要だと思うものを紹介します。

同期と非同期

performBlockは非同期であり、すぐに戻り、ブロックは将来のある時点で、いくつかの非公開スレッドで実行されます。 performBlockを介してMOCに与えられたすべてのブロックは、追加された順序で実行されます。

performBlockAndWaitは、呼び出しスレッドがブロックが実行されるまで待機してから戻るという点で同期しています。ブロックが他のスレッドで実行されるか、呼び出しスレッドで実行されるかはそれほど重要ではなく、信頼できない実装の詳細です。

ただし、「やあ、他のスレッド、このブロックを実行してください。完了したと言われるまで何もせずにここに座っていきます。」として実装できることに注意してください。または、「ねえ、コアデータ、他のすべてのブロックが実行されないようにロックして、自分のスレッドでこのブロックを実行できるようにする」として実装することもできます。または、他の方法で実装することもできます。繰り返しになりますが、実装の詳細はいつでも変更される可能性があります。

ただし、これを最後にテストしたときに、performBlockAndWaitが呼び出しスレッドでブロックを実行しました(上の段落の2番目のオプションを意味します)。これは実際に何が起こっているのかを理解するのに役立つ情報であり、決して信頼するべきではありません。

再入可能

performBlockalways非同期なので、再入可能ではありません。まあ、performBlockで呼び出されたブロック内から呼び出すことができるという点で、これを再入可能と考える人もいます。ただし、これを行うと、performBlockへのすべての呼び出しがすぐに返され、少なくともcurrently実行ブロックがその作業を完全に完了するまでブロックは実行されません。

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

これらの関数は常に次の順序で実行されます:

doSomething()
doSomeMore()
doSomethingElse()

performBlockAndWaitalways同期です。さらに、それはまた再入可能です。複数の呼び出しはデッドロックしません。したがって、別のperformBlockAndWaitの結果として実行されていたブロック内にいるときにperformBlockAndWaitを呼び出してしまった場合、問題はありません。 2回目の呼び出し(およびそれ以降の呼び出し)ではデッドロックが発生しないため、期待どおりの動作が得られます。さらに、2番目のものは、予想どおり、戻る前に完全に実行されます。

[moc performBlockAndWait:^{
    doSomething();
    [moc performBlockAndWait:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

これらの関数は常に次の順序で実行されます:

doSomething()
doSomethingElse()
doSomeMore()

FIFO

FIFOは「先入れ先出し」を意味します。これは、ブロックが内部キューに入れられた順序でブロックが実行されることを意味します。

performBlockは常に内部キューのFIFO構造を尊重します。すべてのブロックはキューに挿入され、削除されたときにのみ実行されますFIFO注文。

定義により、performBlockAndWaitは、すでにエンキューされているブロックのキューをジャンプするため、FIFOの順序付けが壊れます。

performBlockAndWaitで送信されたブロックは、キューで実行されている他のブロックを待つ必要はありません。これを確認する方法はいくつかあります。簡単なのはこれです。

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
    [moc performBlockAndWait:^{
      doSomethingAfterDoSomethingElse();
    }];
    doTheLastThing();
}];

これらの関数は常に次の順序で実行されます:

doSomething()
doSomeMore()
doSomethingAfterDoSomethingElse()
doTheLastThing()
doSomethingElse()

この例ではそれが明らかであるため、私はそれを使用しました。ただし、MOCが複数の場所から呼び出されるものを取得している場合を検討してください。少しわかりにくいかもしれません。

ただし、performBlockAndWaitはプリエンプティブであり、FIFOキューをジャンプできることを覚えておいてください。

デッドロック

performBlockを呼び出すデッドロックは発生しません。ブロック内で愚かなことをすると、デッドロックが発生する可能性がありますが、performBlockを呼び出してもデッドロックは発生しません。どこからでも呼び出すことができ、ブロックをキューに追加して、後で実行するだけです。

特に、外部エンティティが呼び出すことができるメソッドから、またはネストされたコンテキスト内で無差別に呼び出す場合、performBlockAndWaitを呼び出すデッドロックを簡単に取得できます。具体的には、親が子でperformBlockAndWaitを呼び出すと、アプリケーションをデッドロックすることがほぼ保証されます。

ユーザーイベント

Core Dataは「ユーザーイベント」をprocessPendingChangesへの呼び出しの間の何かと見なします。このメソッドが重要である理由の詳細を読むことができますが、「ユーザーイベント」で何が発生するかは、通知、管理の取り消し、伝播の削除、結合の変更などに影響します。

performBlockは、「ユーザーイベント」をカプセル化します。つまり、processPendingChangesへの個別の呼び出し間でコードブロックが自動的に実行されます。

performBlockAndWaitは「ユーザーイベント」をカプセル化しません。ブロックを個別のユーザーイベントとして処理する場合は、自分で行う必要があります。

自動解放プール

performBlockは、独自のautoreleasepoolでブロックをラップします。

performBlockAdWaitは、固有の自動解放プールを提供しません。必要な場合は、自分で提供する必要があります。

個人的な意見

個人的には、performBlockAndWaitを使用するのに十分な理由があるとは思いません。誰かが他の方法では達成できないユースケースを持っていると確信していますが、私はまだそれを見ていません。そのユースケースを誰かが知っている場合は、私と共有してください。

最も近いのは、親コンテキストでperformBlockAndWaitを呼び出すことです(UIがロックされる可能性があるため、NSMainConcurrencyType MOCではこれを行わないでください)。たとえば、現在のブロックが返って他のブロックが実行される前に、データベースが完全にディスクに保存されていることを確認したい場合などです。

したがって、しばらく前に、Core Dataを完全に非同期のAPIとして扱うことにしました。その結果、コアデータコードが大量にあり、テスト以外でperformBlockAndWaitを1回だけ呼び出すことはありません。

人生はこのようにはるかに良いです。 「それは有用でなければならない、または彼らがそれを提供しなかったであろう」と私が思ったときよりもずっと少ない問題を抱えています。

さて、私はもはやperformBlockAndWaitを必要としません。その結果、何かが変わったかもしれませんが、興味がなくなったので見逃してしまいましたが…。

69
Jody Hagins