NSOperation
AFHTTPRequestOperation のサブクラスであるかどうかは重要です。AFHTTPRequestOperation は、method で独自の目的のためにNSOperation
のプロパティを使用します。その場合、プロパティを自分で設定しないでください。completionBlock
setCompletionBlockWithSuccess:failure
completionBlock
AFHTTPRequestOperation の成功と失敗のハンドラーは、メイン スレッドで実行されるようです。
それ以外の場合、 の完了ブロックの実行コンテキストNSOperation
は「未定義」です。つまり、完了ブロックは任意のスレッド/キューで実行できます。実際、それはいくつかのプライベート キューで実行されます。
IMO、実行コンテキストが呼び出しサイトによって明示的に指定されない限り、これは推奨されるアプローチです。インスタンスにアクセス可能なスレッドまたはキュー (メイン スレッドなど) で完了ハンドラーを実行すると、不注意な開発者が簡単にデッド ロックを引き起こす可能性があります。
編集:
親操作の完了ブロックが終了した後に依存操作を開始する場合は、完了ブロックの内容自体を(新しい親) にして、この操作を依存関係として子操作に追加して開始することで解決できます。それをキューに入れます。ただし、これはすぐに扱いにくくなることに気付くかもしれません。NSBlockOperation
別のアプローチでは、より簡潔で簡単な方法で非同期の問題を解決するのに特に適したユーティリティ クラスまたはクラス ライブラリが必要になります。ReactiveCocoaは、このような (簡単な) 問題を解決することができます。ただし、これは過度に複雑であり、実際には「学習曲線」があり、急勾配です。学習に数週間を費やすことに同意し、他の多くの非同期ユースケースやさらに複雑なものを使用することに同意しない限り、お勧めしません。
より単純なアプローチは、JavaScript、Python、Scala、および他のいくつかの言語でかなり一般的な「Promises」を利用することです。
さて、よく読んでください。(簡単な)解決策は実際には以下のとおりです。
"約束" (Future または Deferred と呼ばれることもあります)は、非同期タスクの最終的な結果を表します。あなたのフェッチリクエストはそのような非同期タスクです。ただし、完了ハンドラーを指定する代わりに、非同期メソッド/タスクはPromiseを返します。
-(Promise*) fetchThingsWithURL:(NSURL*)url;
次のように、成功ハンドラー ブロックまたは失敗ハンドラー ブロックを登録して、結果 (またはエラー) を取得します。
Promise* thingsPromise = [self fetchThingsWithURL:url];
thingsPromise.then(successHandlerBlock, failureHandlerBlock);
または、インライン化されたブロック:
thingsPromise.then(^id(id things){
// do something with things
return <result of success handler>
}, ^id(NSError* error){
// Ohps, error occurred
return <result of failure handler>
});
そして短い:
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil);
ここでparseAsync:
は、Promise を返す非同期メソッドを示します。(はい、約束です)。
パーサーから結果を取得する方法を疑問に思うかもしれません。
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
NSLog(@"Parser returned: %@", parserResult);
return nil; // result not used
}, nil);
これにより、実際に async task が開始されますfetchThingsWithURL:
。次に、正常に終了すると、 async task が開始されますparseAsync:
。次に、これが正常に終了すると結果が出力され、それ以外の場合はエラーが出力されます。
複数の非同期タスクを順番に次々と呼び出すことを、「継続」または「連鎖」と呼びます。
上記のステートメント全体が非同期であることに注意してください。つまり、上記のステートメントをメソッドにラップして実行すると、メソッドはすぐに戻ります。
失敗などのエラーをキャッチする方法を疑問に思うかもしれません。fetchThingsWithURL:
parseAsync:
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
NSLog(@"Parser returned: %@", parserResult);
return nil; // result not used
}, nil)
.then(/*succes handler ignored*/, ^id (NSError* error){
// catch any error
NSLog(@"ERROR: %@", error);
return nil; // result not used
});
ハンドラーは、対応するタスクが終了した後に実行されます (もちろん)。タスクが成功すると、成功ハンドラーが呼び出されます (存在する場合)。タスクが失敗した場合、エラー ハンドラが呼び出されます (存在する場合)。
ハンドラーはPromise (またはその他のオブジェクト) を返す場合があります。たとえば、非同期タスクが正常に終了した場合、その成功ハンドラーが呼び出され、別の非同期タスクが開始され、promise が返されます。そして、これが終了したら、さらに別のものを開始することができます。それは「継続」です;)
ハンドラから何でも返すことができます:
Promise* finalResult = [self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
return @"OK";
}, ^id(NSError* error){
return error;
});
ここで、finalResultは最終的に値 @"OK" または NSError になります。
最終的な結果を配列に保存できます。
array = @[
[self task1],
[self task2],
[self task3]
];
すべてのタスクが正常に終了したら続行します。
[Promise all:array].then(^id(results){
...
}, ^id (NSError* error){
...
});
promise の値を設定することは、「解決」と呼ばれます。Promise は 1 回だけ解決できます。
完了ハンドラーまたは完了デリゲートを使用して、任意の非同期メソッドをプロミスを返すメソッドにラップできます。
- (Promise*) fetchUserWithURL:(NSURL*)url
{
Promise* promise = [Promise new];
HTTPOperation* op = [[HTTPOperation alloc] initWithRequest:request
success:^(NSData* data){
[promise fulfillWithValue:data];
}
failure:^(NSError* error){
[promise rejectWithReason:error];
}];
[op start];
return promise;
}
タスクの完了時に、結果値を渡して約束を「履行」するか、理由 (エラー) を渡して「拒否」することができます。
実際の実装によっては、Promise をキャンセルすることもできます。たとえば、リクエスト操作への参照を保持しているとします。
self.fetchUserPromise = [self fetchUsersWithURL:url];
次のように非同期タスクをキャンセルできます。
- (void) viewWillDisappear:(BOOL)animate {
[super viewWillDisappear:animate];
[self.fetchUserPromise cancel];
self.fetchUserPromise = nil;
}
関連する非同期タスクをキャンセルするには、ラッパーに失敗ハンドラーを登録します。
- (Promise*) fetchUserWithURL:(NSURL*)url
{
Promise* promise = [Promise new];
HTTPOperation* op = ...
[op start];
promise.then(nil, ^id(NSError* error){
if (promise.isCancelled) {
[op cancel];
}
return nil; // result unused
});
return promise;
}
注: 成功または失敗のハンドラーは、いつ、どこで、いくつでも登録できます。
このように、Promise を使用して多くのことができます。この簡単な紹介よりもさらに多くのことができます。ここまで読めば、実際の問題を解決する方法がわかるかもしれません。それはすぐそこにあります - それは数行のコードです。
Promise のこの短い紹介は非常に大雑把であり、Objective-C 開発者にとってもまったく新しいものであり、珍しいように聞こえるかもしれないことは認めます。
Promise については、JS コミュニティでたくさん読むことができます。Objective-C には 1 つまたは 3 つの実装があります。実際の実装は、数百行のコードを超えることはありません。たまたま、私はそのうちの1つの作者です:
RX約束。
私はおそらく完全に偏見があり、他のすべての人も Promises を扱ったことがあるようです。;)