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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18291392/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 00:46:45  来源:igfitidea点击:

How does a completion handler work on iOS?

iosobjective-c-blockscompletionhandler

提问by marciokoko

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!

这里我们调用了一个方法,该方法执行操作(执行 TWRequest)并在完成 responseData & urlResponse & error 后返回。只有当它返回时,它才会执行测试授予和停止活动指示器的块。完美的!

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...
  1. fetchUsersWithCompletionHandler 显然是 performRequestWithHandler 的同源物
  2. 不幸的是,这有点复杂,因为有一个 GCD 调用......

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.

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

回答by deekay

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 synchronousrequest - 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 usersArrayis 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.
  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(usersArray);
    
  9. 完成后,它从块中返回并继续消费主队列中的任何内容。

  10. 由于主线程已完成该块,因此后台线程(卡在 dispatch_sync 处)也可以继续进行。在这种情况下,它只是从块返回。


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

Since both are dispatch_asynccalls, 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.

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

So the log output will be like that:

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

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

2.You have:

2.你有:

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

Which declares block typedefe'd as Handlerthat has voidreturn type and that accepts NSArray *as parameter.

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

Later, you have your function:

后来,你有你的功能:

+(void)fetchUsersWithCompletionHandler:(Handler)handler

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

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

And step #8:

和步骤#8:

handler(usersArray);

Which just directly calls handlerblock (like you were calling any C/C++ function) and passes usersArrayas a parameter to it.

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