完了ハンドラとブロックを理解しようとしています。私は、完了ハンドラーがなくても、多くのディーププログラミングにブロックを使用できると思いますが、完了ハンドラーはブロックに基づいていると思います。 (基本的に、完了ハンドラーはブロックを必要としますが、その逆は必要ありません)。
だから私は古いTwitterフレームワークについてこのコードをインターネットで見ました:
[twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (!error) {
self.successLabel.text = @"Tweeted Successfully";
[self playTweetSound];
} else {
// Show alert
}
// Stop indicator
sharedApplication.networkActivityIndicatorVisible = NO;
}];
ここでは、(TWRequestを実行して)処理を行い、responseData&urlResponse&errorで終了したときに戻るメソッドを呼び出しています。戻ったときだけ、テストが許可されたブロックを実行し、アクティビティインジケーターを停止します。パーフェクト!
今、これは私が機能する別のアプリのために持っている設定ですが、私はピースをまとめようとしています:
@interface
Define an ivar
typedef void (^Handler)(NSArray *users);
Declare the method
+(void)fetchUsersWithCompletionHandler:(Handler)handler;
@implementation
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
//...Code to create NSURLRequest omitted...
__block NSArray *usersArray = [[NSArray alloc] init];
//A. Executes the request
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
// Deal with your error
if (error) {
}
NSLog(@"Error %@", error);
return;
}
// Else deal with data
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
// Checks for handler & returns usersArray to main thread - but where does handler come from & how does it know to wait tip usersArray is populated?
if (handler){
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
これが私の理解です:
しかし、基本的には、リクエストが実行されてエラーが処理され、データが処理されてから、ハンドラーがチェックされます。私の質問は、このハンドラ部分はどのように機能するのですか?私はそれが存在するかどうかを理解し、メインキューに送り返し、usersArrayを返します。しかし、usersArrayが入力されるまで待機することをどのようにして知るのでしょうか。私が混乱しているのは、この場合のmethod:blockの中に別のブロック、dispatch_async呼び出しがあることです。私が探しているのは、実際に処理を行い、responseDataとurlResponseを返すようになっていることを知っているロジックだと思います。同じアプリではないことはわかっていますが、performRequestWithHandlerのコードが表示されません。
基本的にこの場合、それはそのように機能します:
これはdispath_asyncなので、現在のスレッドはfetchUsersWithCompletionHandler:メソッドを離れます。
...
バックグラウンドキューに空きリソースができるまで時間が経過します
...
そして今、バックグラウンドキューが解放されると、スケジュールされた操作を消費します(注:synchronousリクエストを実行するため、データを待機します):
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
...
データが来ると、sersArrayが入力されます。
コードはこの部分に続きます:
if (handler){
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
ハンドラーを指定した場合、メインキューでの呼び出し用にブロックがスケジュールされます。これはdispatch_syncなので、メインスレッドがブロックで完了するまで、現在のスレッドでの実行は続行されません。この時点で、バックグラウンドスレッドは辛抱強く待機しています。
...
別の瞬間が過ぎる
...
これで、メインキューにいくつかの空きリソースがあるため、ブロックの上で消費し、このコードを実行します(以前に入力されたusersArrayを「ハンドラー」に渡します)。
handler(usersArray);
完了すると、ブロックから戻り、メインキューにあるものはすべて消費し続けます。
編集:あなたが尋ねた質問に関しては:
メイン/バックグラウンドキューが常にビジーであるようではなく、単にビジーである可能性があります。 (バックグラウンドキューがメインのような並列操作をサポートしていないと仮定します)。メインスレッドで実行されている次のコードを想像してください。
dispatch_async(dispatch_get_main_queue(), ^{
//here task #1 that takes 10 seconds to run
NSLog(@"Task #1 finished");
});
NSLog(@"Task #1 scheduled");
dispatch_async(dispatch_get_main_queue(), ^{
//here task #2 that takes 5s to run
NSLog(@"Task #2 finished");
});
NSLog(@"Task #2 scheduled");
どちらもdispatch_async
呼び出しであるため、これらを次々に実行するようにスケジュールします。ただし、タスク#2はすぐにメインキューによって処理されません。最初に現在の実行ループを終了する必要があるためです。次に、最初にタスク#1を終了する必要があります。
したがって、ログ出力は次のようになります。
Task #1 scheduled
Task #2 scheduled
Task #1 finished
Task #2 finished
2.あなたは持っています:
typedef void (^Handler)(NSArray *users);
Handler
の戻り値の型を持ち、NSArray *
をパラメーターとして受け入れるvoid
として定義されたブロックタイプを宣言します。
その後、あなたはあなたの機能を持っています:
+(void)fetchUsersWithCompletionHandler:(Handler)handler
これは、タイプHandler
のパラメーターブロックとして受け取り、ローカル名handler
を使用してそれにアクセスできるようにします。
そしてステップ#8:
handler(usersArray);
これはhandler
ブロックを直接呼び出し(C/C++関数を呼び出しているように)、usersArray
をパラメーターとしてそれに渡します。