完成处理程序如何在iOS上运行?

时间:2013-08-17 17:13:53

标签: ios objective-c-blocks completionhandler

我试图理解完成处理程序&块。我相信你可以在没有完成处理程序的情况下使用块进行许多深度编程,但我认为我理解完成处理程序是基于块的。 (所以基本上完​​成处理程序需要块,但不是相反)。

所以我在互联网上看到了关于旧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&错误。只有当它返回时才会执行测试授予的块并停止活动指示器。 PERFECT!

现在这是我为不同的应用程序设置的设置,但我正在尝试将各个部分组合在一起:

@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呼叫......
  3. 但基本上,执行请求并处理错误,处理数据然后检查处理程序。我的问题是,这个处理程序部分如何工作?我知道如果它存在,那么它将发送回主队列并返回usersArray。但是,如果等到usersArray被填充,它怎么知道呢?我想有什么让我感到困惑的事实是,在这种情况下,方法:block中有另一个块,即dispatch_async调用。我想我正在寻找的是实际做事的逻辑,并知道WHEN返回responseData和urlResponse。我知道它不是同一个应用程序,但我看不到performRequestWithHandler的代码。

1 个答案:

答案 0 :(得分:29)

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

  1. 你可以从你喜欢的任何线程调用fetchUsersWithCompletionHandler :(可能是主线程)。
  2. 初始化NSURLRequest,然后调用: dispatch_async(dispatch_get_global_queue ... 它基本上创建了块,并安排它在后台队列上进行处理。
  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中)也可以继续进行。在这种情况下,它只是从块返回。

  11. 编辑: 至于你问的问题:

    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");
      
    2. 由于两者都是dispatch_async次调用,因此您可以依次安排这些调用。但是主要队列不会立即处理任务#2,因为首先它必须离开当前执行循环,其次,它必须首先完成任务#1。

      因此日志输出将是这样的:

      Task #1 scheduled
      Task #2 scheduled
      Task #1 finished
      Task #2 finished
      

      2.你有:

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

      其中声明阻止类型为Handler,其void返回类型且接受NSArray *作为参数。

      稍后,你有你的功能:

      +(void)fetchUsersWithCompletionHandler:(Handler)handler
      

      作为Handler类型的参数块,允许使用本地名称handler访问它。

      步骤#8:

      handler(usersArray);
      

      只是直接调用handler块(就像你调用任何C / C ++函数一样)并将usersArray作为参数传递给它。