完成处理程序如何在iOS上运行?(How does a completion handler work on iOS?)

IPhone IT屋
百度翻译此文   有道翻译此文
问 题

Im trying to understand completion handlers & blocks. I believe you can use blocks for many deep programming things without completion handlers, but I think i understand that completion handlers are based on blocks. (So basically completion handlers need blocks but not the other way around).

So I saw this code on the internet about the old twitter framework:

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

Here we are calling a method which does stuff (performs TWRequest) and returns when finished with responseData & urlResponse & error. Only when it returns does it execute the block which tests granted and stops the activity indicator. PERFECT!

Now this is a setup I have for a different app which works, but I'm trying to put the pieces together:

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

Here is my understanding:

  1. fetchUsersWithCompletionHandler is obviously the homologue of performRequestWithHandler
  2. unfortunately this is a little more complex because there is a GCD call in the way...

But basically, the request is executed and the error is dealt with, the data is dealt with and then, the handler is checked. My question is, how does this handler part work? I understand if it exists then it will send back to the main queue and return the usersArray. But how does it know to wait until usersArray is populated? I guess whats confusing me is that fact that the method:block in this case has another block inside of it, the dispatch_async call. I guess what Im looking for is the logic that actually does stuff and knows WHEN to return the responseData and urlResponse. I know its not the same app, but I cant see the code for performRequestWithHandler.

解决方案

Basically in this case it works like that:

  1. You call fetchUsersWithCompletionHandler: from whatever thread you like (probably form main one).
  2. It initializes NSURLRequest, then calls: dispatch_async(dispatch_get_global_queue... which basically creates block, and schedules it for processing on a background queue.
  3. Since it is dispath_async, current thread leaves the fetchUsersWithCompletionHandler: method.

    ...
    time passes, till background queue has some free resources
    ...

  4. And now, when the background queue is free, it consumes scheduled operation (Note: It performs synchronous request - so it waits for data):

    NSURLResponse *response;
    NSError *error = nil;
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                 returningResponse:&response
                                                             error:&error];
    ...
    
  5. Once data comes, then the usersArray is populated.

  6. Code continues to this part:

    if (handler){
        dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
        });
    }
    
  7. Now, if we have handler specified, it schedules block for invocation on a main queue. It is dispatch_sync, so execution on current thread won't proceed till main thread will be done with the block. At this point, background thread patiently waits.

    ...
    another moment passes
    ...

  8. Now main queue has some free resources, so it consumes above block, and executes this code (passing previously populated usersArray to the 'handler'):

    handler(usersArray);
    
  9. Once it is done, it returns from the block and continues consuming whatever it is in the main queue.

  10. Since main thread is done with the block, also background thread (stuck at dispatch_sync) can proceed further. In this case it simply returns from block.

Edit: As for the questions you asked:

  1. It's not like main/background queue will be always busy, it's just it may be. (assuming background queue does not support concurrent operations like the main one). Imagine following code, that is being executed on a main thread:

        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");
    

Since both are dispatch_async calls, you schedule these for execution one after another. But task #2 won't be processed by main queue immediately, since first it has to leave current execution loop, secondly, it has to first finish task #1.

So the log output will be like that:

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

2.You have:

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

Which declares block typedefe'd as Handler that has void return type and that accepts NSArray * as parameter.

Later, you have your function:

+(void)fetchUsersWithCompletionHandler:(Handler)handler

Which takes as a parameter block of type Handler and allow access to it using local name handler.

And step #8:

handler(usersArray);

Which just directly calls handler block (like you were calling any C/C++ function) and passes usersArray as a parameter to it.

本文地址:IT屋 » How does a completion handler work on iOS?

问 题

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



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



  [twitterFeed performRequestWithHandler:^(NSData * responseData,NSHTTPURLResponse * urlResponse,NSError * error){
if(!error){
self.successLabel.text = @“Tweeted Successfully”;
[self playTweetSound];
} else {
//显示提示
}
//停止指示符
sharedApplication.networkActivityIndi​​catorVisible = NO;
}];


这里我们调用一个做东西的方法(执行TWRequest)并在完成responseData& urlResponse&错误。只有当它返回时才会执行测试授予的块并停止活动指示器。完美!



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



  @interface 
定义一个ivar
typedef void(^ Handler)(NSArray * users);
声明方法
+(void)fetchUsersWithCompletionHandler :(处理程序)处理程序;

@implementation
+(void)fetchUsersWithCompletionHandler :(处理程序)处理程序{
//...Code创建NSURLRequest省略...
__block NSArray * usersArray = [[NSArray alloc] init];

// A.执行请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0),^ {

//执行请求
NSURLResponse *响应;
NSError * error = nil;
NSData * receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:& response
error:& error];
//处理错误
if(error) {
}
NSLog(@“Error%@”,错误);
返回;
}
// Else处理数据
NSString * responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];

//检查处理程序& ;将usersArray返回给主线程 - 但处理程序来自何处?如何知道等待tipArray是否填充?
if(handler){
dispatch_sync(dispatch_get_main_queue(),^ {
handler(usersArray);
});
}
});
}


以下是我的理解:




  1. fetchUsersWithCompletionHandler显然是performRequestWithHandler的同系物

  2. 不幸的是,这有点复杂,因为在路上有一个GCD调用...



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


解决方案

基本上在这种情况下它的工作方式就是这样:




  1. 你可以从你喜欢的任何线程调用fetchUsersWithCompletionHandler :(可能是主线程)。

  2. 它初始化NSURLRequest,然后调用:
    dispatch_async(dispatch_get_global_queue ...
    基本上创建块,并安排它在后台队列上进行处理。

  3. 由于它是dispath_async,当前线程离开fetchUsersWithCompletionHandler:方法。



    ...

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

    ...


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



      NSURLResponse * response; 
    NSError * error = nil;
    NSData * receivedData = [NSURLConnection sendSynchronousRequest:请求
    returnsResponse:& 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)也可以继续进行。在这种情况下,它只是从块返回。






编辑:
As对于你提出的问题:




  1. 这不像主/后台队列总是很忙,它可能就是这样。 (假设后台队列不支持主要的并发操作)。想象一下以下代码,即在主线程上执行:



      dispatch_async(dispatch_get_main_queue(),^ {
    / /这里任务#1需要10秒才能运行
    NSLog(@“任务#1完成”);
    });
    NSLog(@“任务#1预定”);

    dispatch_async(dispatch_get_main_queue(),^ {
    //这里任务#2花费5s来运行
    NSLog(@“任务#2完成”);
    });
    NSLog(@“任务#2预定”);



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



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



 任务#1预定
任务#2预定
任务#1完成
任务#2完成


2.你有:



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


其中声明块类型为处理程序具有 void 返回类型,并接受 NSArray * 作为参数。



稍后,你有你的功能:



  +(void) fetchUsersWithCompletionHandler :(处理程序)处理程序


作为 Handler类型的参数块并允许使用本地名称处理程序进行访问。



步骤8:



  handler(usersArray); 


其中只是直接调用处理程序块(如你正在调用任何C / C ++函数)并将 usersArray 作为参数传递给它。


本文地址:IT屋 » 完成处理程序如何在iOS上运行?

官方微信
扫一扫关注IT屋
微信公众号搜索 “ IT屋 ” ,选择关注
与百万开发者在一起