完成处理程序在iOS上如何工作?

我试图了解完成处理程序和块。 我相信你可以使用块进行许多深层次的编程,而不需要完成处理程序,但是我想我明白,完成处理程序是基于块的。 (所以基本上完​​成处理程序需要块,而不是相反方向)。

所以我在互联网上看到了关于旧的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时返回。 只有当它返回时,才会执行被testing的块并停止活动指示器。 完善!

现在,这是一个设置,我有一个不同的应用程序的工作,但我试图把碎片放在一起:

 @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); }); } }); } 

这是我的理解:

  1. fetchUsersWithCompletionHandler显然是performRequestWithHandler的同系词
  2. 不幸的是这有点复杂,因为有一个GCD电话的方式…

但基本上,请求被执行并且错误被处理,数据被处理,然后处理器被检查。 我的问题是,这个处理程序部分是如何工作的? 我知道如果它存在,那么它会发回到主队列,并返回usersArray。 但是,它是如何知道要等到usersArray被填充的呢? 我想什么让我感到困惑的是,在这种情况下,方法:块中有另一个块,dispatch_async调用。 我想我在找什么是真正的东西的逻辑,知道什么时候返回responseData和urlResponse。 我知道它不是一样的应用程序,但我不能看到performRequestWithHandler的代码。

基本上在这种情况下它的工作原理是这样的:

  1. 你可以调用fetchUsersWithCompletionHandler:从任何你喜欢的线程(可能是主要的)。
  2. 它初始化NSURLRequest,然后调用:dispatch_async(dispatch_get_global_queue …基本上创build块,并调度它在后台队列处理。
  3. 由于是dispath_async,所以当前线程离开fetchUsersWithCompletionHandler:方法。


    时间过去,直到后台队列有一些免费的资源

  4. 而现在,当后台队列空闲时,它会消耗定时操作(注意:它执行同步请求 – 所以它等待数据):

     NSURLResponse *response; NSError *error = nil; NSData *receivedData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; ... 
  5. 一旦数据到来,那么usersArray被填充。

  6. 代码继续这个部分:

     if (handler){ dispatch_sync(dispatch_get_main_queue(), ^{ handler(usersArray); }); } 
  7. 现在,如果我们有处理程序指定,它调度主队列调用块。 它是dispatch_sync,所以当前线程的执行将不会继续,直到主线程完成。 此时,后台线程耐心等待。


    另一时刻过去了

  8. 现在主队列有一些免费的资源,所以它消耗上面的块,并执行这个代码(将先前填充的usersArray传递给'handler'):

     handler(usersArray); 
  9. 一旦完成,它将从块中返回并继续消耗主队列中的所有内容。

  10. 由于主线程是用块完成的,所以后台线程(卡在dispatch_sync中)可以继续进行。 在这种情况下,它只是从块返回。

编辑:至于你问的问题:

  1. 这不是像主/背景队列将总是忙,它只是它可能。 (假设后台队列不支持主要的并发操作)。 想象下面的代码,它正在主线程上执行:

      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 

你有:

 typedef void (^Handler)(NSArray *users); 

其中声明块typededef作为Handler程序具有void返回types,并接受NSArray *作为参数。

后来,你有你的function:

 +(void)fetchUsersWithCompletionHandler:(Handler)handler 

这需要作为Handlertypes的参数块,并允许使用本地名称handler访问它。

步骤#8:

 handler(usersArray); 

它只是直接调用handler块(就像您正在调用任何C / C ++函数)并将usersArray作为parameter passing给它。