当您感兴趣的任务完成时,完成处理程序执行块的原因是什么?

时间:2014-01-21 21:53:08

标签: objective-c block completionhandler

我一直在询问并试图了解完成处理程序的工作原理。我已经使用了很多,我已经阅读了很多教程。我将发布我在这里使用的那个,但我希望能够创建自己的,而不使用别人的代码作为参考。

我理解这个调用方法的完成处理程序:

-(void)viewDidLoad{
[newSimpleCounter countToTenThousandAndReturnCompletionBLock:^(BOOL completed){
        if(completed){ 
            NSLog(@"Ten Thousands Counts Finished");
        }
    }];
}

然后在被调用的方法中:

-(void)countToTenThousandAndReturnCompletionBLock:(void (^)(BOOL))completed{
    int x = 1;
    while (x < 10001) {
        NSLog(@"%i", x);
        x++;
    }
    completed(YES);
}

然后我根据许多SO帖子提出了这个问题:

- (void)viewDidLoad{
    [self.spinner startAnimating];
    [SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
        self.usersArray = users;
        [self.tableView reloadData];
    }];
}

在调用此方法后将使用收到的数据用户重新加载tableview:

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

+(void)fetchUsersWithCompletionHandler:(Handler)handler {
    NSURL *url = [NSURL URLWithString:@"http://www.somewebservice.com"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
    [request setHTTPMethod: @"GET"];
    **// We dispatch a queue to the background to execute the synchronous NSURLRequest**
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        // Perform the request
        NSURLResponse *response;
        NSError *error = nil;
        NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                     returningResponse:&response
                                                                 error:&error];
        if (error) { **// If an error returns, log it, otherwise log the response**
            // Deal with your error
            if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
                NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
                NSLog(@"HTTP Error: %d %@", httpResponse.statusCode, error);
                return;
            }
            NSLog(@"Error %@", error);
            return;
        }
        **// So this line won't get processed until the response from the server is returned?**
        NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];

        NSArray *usersArray = [[NSArray alloc] init];
        usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
        // Finally when a response is received and this line is reached, handler refers to the block passed into this called method...so it dispatches back to the main queue and returns the usersArray
        if (handler){
            dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
            });
        }
    });
}

我可以在计数器示例中看到,被调用的方法(带有传递的块)在完成之前永远不会退出循环。因此'完成'部分实际上取决于被调用方法中的代码,而不是传递给它的块?

在这种情况下,“完成”部分取决于对NSURLRequest的调用是同步的。如果它是异步的怎么办?在我的数据由NSURLResponse填充之前,我怎么能够阻止调用该块?

2 个答案:

答案 0 :(得分:3)

您的第一个示例是正确和完整的,是了解完成块的最佳方法。他们没有进一步的魔力。它们不会自动执行。当一段代码调用它们时执行它们。

正如您所注意到的,在后一个示例中,很容易在正确的时间调用完成块,因为一切都是同步的。如果它是异步的,那么您需要将块存储在实例变量中,并在异步操作完成时调用它。操作完成时可以安排通知(可能使用完成处理程序)。

将块存放在ivar中时要小心。您的一个例子包括:

   self.usersArray = users;

self的调用将导致该块保留self(调用对象)。这可以轻松创建保留循环。通常,您需要对此self进行弱引用:

- (void)viewDidLoad{
  [self.spinner startAnimating];
  __weak typeof(self) weakSelf = self;
  [SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
      [strongSelf setUsersArray:users];
      [[strongSelf tableView] reloadData];
    }
  }];
}

这是weakSelf / strongSelf模式的一个相当迂腐的版本,在这种情况下它可以做得更简单,但它展示了你可能需要的所有部分。您对self采用弱引用,这样就不会创建保留循环。然后,在完全阻止中,你需要一个强引用self,以便它不会在你的块中间消失。然后确保self实际上仍然存在,然后才继续。 (由于消息nil是合法的,您可以在此特定情况下跳过strongSelf步骤,并且它将是相同的。)

答案 1 :(得分:2)

您的第一个示例(countToTenThousandAndReturnCompletionBLock)实际上是同步方法。完成处理程序在这里没有多大意义:或者,您可以在假设方法countToTenThousand之后立即调用该块(基本相同,只是没有完成处理程序)。

您的第二个示例fetchUsersWithCompletionHandler:是一种异步方法。然而,它实际上非常不理想:

  1. 它应以某种方式通知呼叫站点请求可能已失败。也就是说,要么为完成处理程序提供附加参数,例如, “NSError* error或我们一个参数id result。在第一种情况下,错误数组不是nil,而是第二种情况,单个参数 result 可以是错误对象(有点NSError),也可以是实际结果(有点NSArray)。

  2. 如果您的请求失败,您将错过向呼叫站点发出错误信号。

  3. 有代码味道:

    事实上,系统实现的底层网络代码是异步的。但是,使用的方便类方法sendSynchronousRequest:同步。这意味着,作为sendSynchronousRequest:的实现细节,调用线程被阻止,直到网络响应的结果可用为止。而this_blocking_只占用等待的整个线程。创建一个线程是非常昂贵的,只是为了这个目的是一种浪费。这是第一个代码味道。是的,只是使用方便的类方法sendSynchronousRequest:本身就是错误的编程实践!

    然后在您的代码中,通过将此同步请求调度到队列,再次使该同步请求异步。

    因此,您最好使用异步方法(例如sendAsynchronous...)来处理网络请求,该方法可通过完成处理程序发出完成信号。然后,此完成处理程序可以调用您的完成处理程序参数,负责您是否获得实际结果或错误。