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

18

我正在尝试理解完成处理程序和块。我相信你可以使用块来进行许多深入的编程,而不需要完成处理程序,但我认为完成处理程序是基于块的。 (因此基本上完成处理程序需要块,但反之则不然)。

所以我在互联网上看到了这段关于旧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的方法。只有在它返回时,才会执行测试granted并停止活动指示器的块。完美!

现在,这是我为另一个应用程序设置的方式,它可以正常工作,但我正在努力把所有的部分组合起来:

@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 被填充?我想混淆我的是该方法:block 在此情况下具有另一个块,即 dispatch_async 调用。我想我要找的是实际执行操作并知道何时返回 responseData 和 urlResponse 的逻辑。我知道这不是同一个应用程序,但我看不到 performRequestWithHandler 的代码。

1个回答

29

基本上在这种情况下,它的工作方式如下:

  1. 您可以从任何线程(可能是主线程)调用 fetchUsersWithCompletionHandler:。
  2. 它初始化 NSURLRequest 然后调用: dispatch_async(dispatch_get_global_queue... 这基本上创建了一个 block,并将其安排在后台队列上进行处理。
  3. 由于它是 dispath_async,当前线程离开 fetchUsersWithCompletionHandler: 方法。

    ...
    时间过去了,直到后台队列有一些空闲资源
    ...

  4. 现在,当后台队列空闲时,它会消耗已安排的操作(注意:它执行同步请求 - 因此它等待数据):

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

  6. 代码继续执行到这里:

  7. if (handler){
        dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
        });
    }
    
  8. 现在,如果我们指定了处理程序,它会将块安排在主队列上进行调用。这是dispatch_sync,因此在当前线程上的执行在主线程完成块后才会继续进行。此时,后台线程会耐心等待。

    ...
    又过了一段时间
    ...

  9. 现在主队列有一些空闲资源,因此它会消耗上面的块,并执行此代码(将先前填充的usersArray传递给“handler”):

    handler(usersArray);
    
  10. 一旦完成,它就会从块返回并继续使用主队列中的任何内容。

  11. 由于主线程已经完成了该块,后台线程(在dispatch_sync处阻塞)也可以继续执行。在这种情况下,它只是从块返回。

编辑: 至于您提出的问题:

  1. 不是说主/后台队列总是会很忙,而是可能会很忙。(假设后台队列不像主队列那样支持并发操作)。想象以下代码,在主线程上执行:

  2.     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);
    

    声明了一个typedef为Handler的代码块,它具有void返回类型并接受NSArray *作为参数。

    后来你有了自己的函数:

    +(void)fetchUsersWithCompletionHandler:(Handler)handler
    

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

    第8步:

    handler(usersArray);
    

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


2个问题:(1)为什么在第3步中说“时间流逝,直到后台队列有一些空闲资源”。为什么它不会有空闲资源?在第7/8步中也是如此,主队列为什么不会有空闲资源,因此必须等待?最重要的是,处理程序如何“处理”usersArray以将其发送回来?代码的哪个部分告诉处理程序将数据发送回调用他的人? - marciokoko
NSData转换为NSString,然后再转换回NSData的必要性是什么? - Rishab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接