18

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

所以我在网上看到了这段关于旧推特框架的代码:

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

以下是我的理解:

  1. fetchUsersWithCompletionHandler 显然是 performRequestWithHandler 的同系物
  2. 不幸的是,这有点复杂,因为途中有一个 GCD 调用......

但基本上,执行请求并处理错误,处理数据,然后检查处理程序。我的问题是,这个处理程序部分是如何工作的?我知道如果它存在,那么它将发送回主队列并返回 usersArray。但是它怎么知道要等到 usersArray 被填充?我想让我感到困惑的是,在这种情况下,method:block 内部还有另一个块,即 dispatch_async 调用。我想我正在寻找的是真正做事情的逻辑,并且知道何时返回 responseData 和 urlResponse。我知道它不是同一个应用程序,但我看不到 performRequestWithHandler 的代码。

4

1 回答 1

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 处)也可以继续进行。在这种情况下,它只是从块中返回。

编辑:至于你问的问题:

  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

2.你有:

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

它将块 typedefe 声明为Handler具有void返回类型并接受NSArray *作为参数。

后来,你有你的功能:

+(void)fetchUsersWithCompletionHandler:(Handler)handler

它作为类型的参数块Handler并允许使用 local name 访问它handler

第 8 步:

handler(usersArray);

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

于 2013-08-17T18:10:37.770 回答