web-dev-qa-db-ja.com

iOS 6の完了ブロックのdispatch_get_current_queue()の代替手段

ブロックと完了ブロックを受け入れるメソッドがあります。最初のブロックはバックグラウンドで実行し、完了ブロックはメソッドが呼び出されたキューで実行する必要があります。

後者の場合、私は常にdispatch_get_current_queue()を使用しましたが、iOS 6以降では非推奨のようです。代わりに何を使うべきですか?

98
cfischer

「発信者がいたキューで実行」のパターンは魅力的ですが、最終的には素晴らしいアイデアではありません。そのキューは、優先度の低いキュー、メインキュー、または他の奇妙なプロパティを持つキューです。

これに対する私のお気に入りのアプローチは、「完了ブロックはこれらのプロパティを持つ実装定義キューで実行されます:x、y、z」と言い、呼び出し元がそれ以上の制御を必要とする場合、特定のキューにブロックをディスパッチさせます。指定するプロパティの典型的なセットは、「シリアル、非リエントラント、および他のアプリケーション可視キューに関する非同期」のようなものです。

**編集**

Catfish_Manは、以下のコメントに例を示しました。私は彼の答えにそれを追加しています。

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
63
Catfish_Man

これは、基本的に、説明しようとしているAPIにとって間違ったアプローチです。 APIがブロックと完了ブロックを受け入れて実行する場合、次の事実が真である必要があります。

  1. 「実行するブロック」は、内部キューで実行する必要があります。 API専用のキュー。そのため、そのAPIの制御下にあります。これの唯一の例外は、ブロックがメインキューまたはグローバルコンカレントキューの1つで実行されることをAPIが明示的に宣言している場合です。

  2. 完了ブロックは、alwaysを、タプル(キュー、ブロック)として表現する必要があります。完了ブロックは、既知のグローバルキューで実行されます。完了ブロックはさらに、渡されたキューで非同期にディスパッチされる必要があります。

これらは単なる文体的なポイントではなく、APIがデッドロックや他の状況でいつか最も近くのツリーからハングアップする他のエッジケースの動作から安全であるためには完全に必要です。 :-)

24
jkh

他の答えは素晴らしいですが、私にとって答えは構造的です。私はシングルトンにあるこのようなメソッドを持っています:

- (void) dispatchOnHighPriorityNonMainQueue:(simplest_block)block forceAsync:(BOOL)forceAsync {
    if (forceAsync || [NSThread isMainThread])
        dispatch_async_on_high_priority_queue(block);
    else
        block();
}

次の2つの依存関係があります。

static void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

そして

typedef void (^simplest_block)(void); // also could use dispatch_block_t

そのようにして、他のスレッドでディスパッチする呼び出しを集中化します。

14
Dan Rosenstark

そもそもdispatch_get_current_queueの使用に注意する必要があります。ヘッダーファイルから:

デバッグとロギングの目的のみに推奨:

コードは、グローバルキューのいずれかまたはコード自体が作成したキューでない限り、返されるキューについて仮定してはなりません。コードは、キューがdispatch_get_current_queue()によって返されたキューでない場合、キューへの同期実行がデッドロックから安全であると想定してはなりません。

次の2つのいずれかを実行できます。

  1. 最初に投稿したキューへの参照を保持し(dispatch_queue_createで作成した場合)、それ以降はそれを使用します。

  2. dispatch_get_global_queueを介してシステム定義のキューを使用し、使用しているキューを追跡します。

事実上、あなたがいるキューを追跡するためにシステムに依存している間、あなたは自分でそれをしなければなりません。

12
WDUK

キューの比較にまだ必要な人は、ラベルまたは指定によってキューを比較できます。これを確認してください https://stackoverflow.com/a/23220741/1531141

4
alexey.hippie

Appleはdispatch_get_current_queue()を非推奨にしましたが、他の場所に穴を残していたため、現在のディスパッチキューを取得できます。

if let currentDispatch = OperationQueue.current?.underlyingQueue {
    print(currentDispatch)
    // Do stuff
}

これは、少なくともメインキューで機能します。 underlyingQueueプロパティはiOS 8以降で使用可能です。

元のキューで完了ブロックを実行する必要がある場合は、 OperationQueue を直接使用することもできます。つまり、GCDを使用しません。

4
kelin

これは私も答えです。そこで、ユースケースについてお話します。

(他のレイヤーの中に)サービスレイヤーとUIレイヤーがあります。サービス層は、バックグラウンドでタスクを実行します。 (データ操作タスク、CoreDataタスク、ネットワーク呼び出しなど)。サービス層には、UI層のニーズを満たすための操作キューがいくつかあります。

UIレイヤーは、サービスレイヤーに依存して作業を行い、成功完了ブロックを実行します。このブロックにはUIKitコードを含めることができます。単純な使用例は、サーバーからすべてのメッセージを取得し、コレクションビューをリロードすることです。

ここでは、サービス層に渡されるブロックが、サービスが呼び出されたキューにディスパッチされることを保証します。 dispatch_get_current_queueは非推奨のメソッドであるため、NSOperationQueue.currentQueueを使用して呼び出し元の現在のキューを取得します。このプロパティに関する重要な注意。

通常、実行中の操作のコンテキスト外からこのメソッドを呼び出すと、nilが返されます。

常に既知のキュー(カスタムキューとメインキュー)でサービスを呼び出すため、これはうまく機能します。 serviceAがserviceCを呼び出すことができるserviceBを呼び出すことができる場合があります。最初のサービスコールの発信元を制御するため、残りのサービスは同じルールに従います。

したがって、NSOperationQueue.currentQueueは常にキューまたはMainQueueのいずれかを返します。

0