performBlock: と performBlockAndWait: の動作の違いは何ですか? 質問する

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

もう一度言いますが、それがコンテキストを保存する正しい場所だと思います... 両方の方法の違いは何ですか (この場合、違いがあれば)?

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

前もって感謝します

ベストアンサー1

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

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

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

事実上の相違

違いは数多くあります。他にもたくさんあると思いますが、ここでは理解しておくべき最も重要な違いを挙げてみました。

同期と非同期

performBlockは非同期であり、すぐに戻り、ブロックは将来のある時点で、非公開のスレッドで実行されます。MOC に渡されたすべてのブロックは、performBlock追加された順序で実行されます。

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

ただし、これは「ねえ、他のスレッドでこのブロックを実行してください。完了したと通知されるまで、私は何もせずにここにいます」というように実装される可能性もあります。または、「ねえ、Core Data さん、他のすべてのブロックが実行されないようにロックをかけて、このブロックを自分のスレッドで実行できるようにしてください」というように実装される可能性もあります。または、他の方法で実装される可能性もあります。繰り返しますが、これはいつでも変更される可能性がある実装の詳細です。

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

再入性

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

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

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

doSomething()
doSomeMore()
doSomethingElse()

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

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

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

doSomething()
doSomethingElse()
doSomeMore()

先入れ先出し

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

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

定義上、performBlockAndWaitすでにキューに登録されているブロックのキューをジャンプするため、FIFO 順序が破られます。

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

[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一意の autoreleasepool は提供されません。必要な場合は、自分で提供する必要があります。

個人的な意見

個人的には、 を使用する良い理由はあまりないと思いますperformBlockAndWait。他の方法では実現できないユースケースがあるのは確かですが、私はまだ見たことがありません。そのようなユースケースを知っている方がいたら、ぜひ教えてください。

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

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

こうすることで、生活はずっと良くなりました。「きっと役に立つはずだ、そうでなければ提供されないだろう」と思っていた頃よりも、問題がずっと少なくなります。

今では、 はもう必要ありませんperformBlockAndWait。 結果として、 が多少変化し、興味がなくなったために見逃しただけかもしれませんが... おそらくそうではないと思います。

おすすめ記事