実際の問題は、「非同期メソッドからどのように結果が返されるか」です。
たとえば、「doSomethingAsync」という非同期タスクがあるとします (これはクラス メソッド、インスタンス メソッド、または関数ですが、実際には問題ではありません)。
おなじみの同期フォーム 「doSomething」は、単純に結果を返し、次のように宣言できます。
- (Result*) doSomething;
同等の非同期タスク「doSomethingAsync」は、完了ハンドラを使用して宣言できます。
typedef void (^completion_block_t)(Result* result)
- (void) doSomethingAsync:(completion_block_t)completionHandler;
例:
クラス "MyClass" が、非同期クラス メソッド (クラス Foo) の結果から初期化されるプロパティ "result" を定義するとします。メソッド「fetchResult」で結果を取得します。
- (void) fetchResult {
[Foo doSomethingAsync:^(Result* result){
self.result = result;
}];
}
ここで何が起こっているのかを把握するには時間がかかる場合があり、「非同期で考える」必要があります;)
重要なことは、完了ハンドラーがブロックであるということです。ブロックはインラインで定義され、通常のオブジェクトであるかのように扱われます。ブロックは呼び出しサイトによって作成され、パラメーターcompletionHandlerの引数としてメソッドに渡されますdoSomethingAsync:
。ブロック自体は、非同期タスクの完了時に実行するアクションを定義します。
一方、非同期メソッドは、完了するまでこのブロックへの参照を内部的に保持する必要があります。次に、ブロックを呼び出し、その結果を引数として完了ブロックのパラメーター結果に提供する必要があります。
非同期関数の結果を「返す」形式は他にもあります。一般的なパターンの 1 つは、FutureまたはPromiseを使用することです。Future または Promiseは、非同期関数の最終的な結果を単純に表します。これは、非同期関数からすぐに返すことができるオブジェクトですが、その値(非同期タスクの結果) は、非同期タスクが終了した後でのみ使用できます。タスクは、終了時に最終的にプロミスの値を設定する必要があります。これを「解決」といいます。つまり、タスクは返された promise オブジェクトへの参照を保持し、最後に成功を意味する値で「解決」する必要があります。または失敗を意味する値。
そのようなクラス「Promise」があると仮定すると、これにより、次のような非同期メソッドを宣言できます。
- (Promise*) doSomethingAsync;
Promise の実装は、「非同期モデル」を完全にサポートしている場合があります。結果を取得するには、結果が得られたときに何をするかを定義するだけです。たとえば、Promise の特定の実装でこれを実現できます。
- (void) fetchResult {
Promise* promise = [Foo doSomethingAsync];
promise.then(^(Result* result){
self.result = result;
});
}
実際には、ブロックを返すクラス Promiseのプロパティである "then" に注意してください。
@property then_block_t then;
タイプ「then_block_t」のこの返されたブロックは、次の方法ですぐに呼び出されます。
promise.then(...)
よく似ています:
then_block_t block = promise.then;
block( ... );
しかし短い。
タイプ「then_block_t」のブロックには、結果が最終的に利用可能になったときにプロミスによって呼び出される完了ブロックであるパラメーターがあります。完了ブロックはインラインで定義されています。
^(Result* result){ ... }
ご覧のとおり、完了ブロックには、非同期メソッドの実際の結果であるパラメーターresultがあります。
OK、頭がぐるぐるするかもしれません ;)
しかし、ここで、例に戻ります
Promise* promise = [Foo doSomethingAsync];
promise.then(^(Result* result){
self.result = result;
});
単純に次のように読みます。
さらに短く書くことができます:
[Foo doSomethingAsync]
.then(^(Result* result) {
self.result = result;
};
これは、完了ハンドラーを使用したフォームに似ています。
[Foo doSomethingAsync:^(Result* result){
self.result = result;
}];
ただし、Promise の最も重要な機能は、2 つ以上の非同期タスクを一緒に「連鎖」できることです。then_block_t
プロパティから返されるtype のブロックには typethen
の戻り値があるため、これが可能になりますPromise
。
typedef Promise* (^then_block_t)(completion_block_t onSuccess);
私はあなたの頭が高頻度で回転していると確信しています;)-したがって、例はこれを明確にします(うまくいけば):
asyncA と asyncB という 2 つの非同期メソッドがあるとします。1 つ目は入力を要求し、それを非同期的に処理して結果を生成します。2 番目のメソッド asyncB は、この結果を受け取り、非同期的に処理し、最終的に @"OK" または NSError を出力する必要があります (問題が発生した場合)。
[self asyncA:input]
.then(^(OutputA* outA) {
return [self asyncB:outA];
})
.then(^(OutputB* outB){
NSLog(@"end result: %@", outB);
return nil;
});
これは次のとおりです。
ハンドラーがステートメントでPromiseオブジェクトを返すことに気付くかもしれません。
return [self asyncB:outA];
.
これにより、タスク「asyncA」から「asyncB」への「チェーン」フォームが確立されます。返されたプロミスの最終的な「値」は、次のハンドラーの結果パラメーターとして表示されます。
ハンドラーは、次のハンドラーでも結果パラメーターとして終了する即時の結果を返すこともあります。
Objective-C での実際の実装は、*then_block_t* に2 つのパラメーターがあるという点で、わずかに異なります。1 つは成功ケース用で、もう 1 つは失敗ケース用です。
typedef Promise* (^then_block_t)(completion_block_t onSuccess, failure_block_t onFailure);
簡潔にするために、以前のサンプルではこれを省略しました。実際の実装は次のようになります。
[self asyncA:input]
.then(^(OutputA* out) {
return [self asyncB:out];
}, nil)
.then(^(id result){
NSLog(@"result: %@", result);
return nil;
}, ^id(NSError*error){
NSLog(@"ERROR: %@", error);
return nil;
});
promise のもう 1 つの優れた機能は、promise のチェーンを通じてエラーが転送されることです。つまり、成功ハンドラのみが定義されている A、B、C、D など、複数の「連鎖」タスクを持つことができます。最後のハンドラー (ペア) は、エラー ハンドラーを定義します。最初の非同期タスクでエラーが発生した場合、そのエラーは、エラー ハンドラーが最終的に処理するまで、すべての promise を通じて転送されます。成功ハンドラーはタスクが成功した場合にのみ呼び出され、エラー ハンドラーはタスクが失敗した場合にのみ呼び出されます。
[self A]
.then(^(id result) {
return [self B:result];
}, nil)
.then(^(id result) {
return [self C:result];
}, nil)
.then(^(id result) {
return [self D:result];
}, nil)
.then(^(id result) {
NSLog(@"Success");
return nil;
}, ^id(NSError*error){
NSLog(@"ERROR: %@", error);
return nil;
});
Promises に関してはさらに多くのことがありますが、この SO の回答をはるかに超えています。
実装の例はここにあります: RXPromise