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

mar*_*oko 18 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;
    }];
Run Code Online (Sandbox Code Playgroud)

这里我们调用一个方法来做东西(执行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);
            });
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

这是我的理解:

  1. fetchUsersWithCompletionHandler显然是performRequestWithHandler的同源
  2. 不幸的是,这有点复杂,因为有一个GCD呼叫的方式......

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

dee*_*kay 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];
    ...
    
    Run Code Online (Sandbox Code Playgroud)
  5. 数据到来后,就会填充usersArray.

  6. 代码继续到这一部分:

    if (handler){
        dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
        });
    }
    
    Run Code Online (Sandbox Code Playgroud)
  7. 现在,如果我们指定了处理程序,它会调度主队列上的调用块.它是dispatch_sync,因此在主线程完成块之前,当前线程的执行不会继续.此时,后台线程耐心等待.

    ......
    另一个时刻过去了
    ......

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

    handler(usersArray);
    
    Run Code Online (Sandbox Code Playgroud)
  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");
    
    Run Code Online (Sandbox Code Playgroud)

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

所以日志输出将是这样的:

Task #1 scheduled
Task #2 scheduled
Task #1 finished
Task #2 finished
Run Code Online (Sandbox Code Playgroud)

你有:

typedef void (^Handler)(NSArray *users);
Run Code Online (Sandbox Code Playgroud)

其中声明了block typedefe'd,因为Handler它具有void返回类型并且接受NSArray *为参数.

之后,你有你的功能:

+(void)fetchUsersWithCompletionHandler:(Handler)handler
Run Code Online (Sandbox Code Playgroud)

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

第8步:

handler(usersArray);
Run Code Online (Sandbox Code Playgroud)

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