3

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

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

-(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 填充之前,我如何才能推迟调用该块?

4

2 回答 2

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一步,它也是一样的。)

于 2014-01-21T23:06:49.357 回答
2

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

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

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

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

  3. 有代码气味:

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

    然后在您的代码中,您通过将这个同步请求分派到队列来再次异步请求它。

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

于 2014-01-21T23:18:03.913 回答