【问题标题】:method returns before completionhandler方法在完成处理程序之前返回
【发布时间】:2015-07-02 07:56:41
【问题描述】:

我正在为我的项目开发一个 networkUtil,我需要一个方法来获取一个 url,并使用 NSURLSessionDataTask 返回从该 url 接收到的 JSON 以从服务器获取 JSON。方法如下:

+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
    __block NSDictionary* jsonResponse;
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);
    }];

    [dataTask resume];

    return jsonResponse;
}

问题是我的方法中的 completionHandler 和 方法本身 在 不同的线程 上运行,最后一行的 jsonResponse 是总是nil

我应该如何使用从 urlString 返回的 json 设置 jsonResponse?
此问题的最佳做法是什么?

【问题讨论】:

  • 这是因为您正在进行异步调用。有很多关于它的问题。

标签: ios objective-c objective-c-blocks completionhandler


【解决方案1】:

在 NSURLSession 中运行的块在不同的线程上运行 - 您的方法不会等待块完成。

这里有两种选择

第一个。发送 NSNotification

+ (void) getJsonDataFromURL:(NSString *)urlString{
       NSURLSession *session = [NSURLSession sharedSession];
       NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
           NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
           NSLog(@"%@", jsonResponse);

           [[NSNotificationCenter defaultCenter] postNotificationName:@"JSONResponse" object:nil userInfo:@{@"response" : jsonResponse}];
       }];

       [dataTask resume];
}

第二个。此实用程序方法的过去完成块

+ (void) getJsonDataFromURL:(NSString *)urlString
            completionBlock:(void(^)(NSDictionary* response))completion {
       NSURLSession *session = [NSURLSession sharedSession];
       NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
           NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
           NSLog(@"%@", jsonResponse);

           completion(jsonResponse);
       }];

       [dataTask resume];
}

【讨论】:

  • 这条线是做什么用的? [[NSNotificationCenter defaultCenter] postNotificationName:@"JSONResponse" object:nil userInfo:@{@"response" : jsonResponse}];问题是我想返回jsonResponse,一个选项是你的第二个选项,我想知道有没有办法返回jsonResponse。
  • 这一行将通知发布到NSNotificationCenter。为什么要return呢?使用第二种方法来执行获得响应所需的任何操作。你问过最佳实践 - 我给你了
  • 问题是在另一种方法中我调用 getJsonDataFromURL 并将其返回值设置为一个变量并对其进行一些处理。 + (NSNumber*) getServerVersion{ NSDictionary* versionUpdateJson = [NetworkUtil getJsonDataFromURL:updateServerURL]; if(versionUpdateJson){ NSNumber* updateVersion = [NSNumber numberWithFloat:[[versionUpdateJson valueForKey:@"serverVersion"] floatValue]]; return updateVersion; }else{ return @-1; } } 这个问题又出现了
  • 我建议采用同样的方法。同样的情况——同样的决定。但我认为更好的是摆脱这个getServerVersion 方法,直接调用getJsonDataFromURL。以防万一,它适合您的应用组织
  • 那么当使用completionHandlers 时我们不能返回块内出现的值并且使用这些值的方法应该作为您的第二种方法,这是真的吗?
【解决方案2】:

有些人可能会说这是一个糟糕的建议,但您也可以同步下载数据。它应该在后台队列中完成。这不是一个最佳实践,但在某些情况下(如命令行实用程序、非关键后台队列)是可以的。

NSURLSession 没有同步下载方法,但是你可以用信号量轻松绕过它:

+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{
    __block NSDictionary* jsonResponse;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // Line 1

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);

        dispatch_semaphore_signal(semaphore); // Line 2
    }];

    [dataTask resume];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // Line 3

    return jsonResponse;
}

NSURLSession 有一个delegateQueue 属性,用于“与会话相关的委托方法调用和完成处理程序”。默认情况下,NSURLSession 总是在初始化期间创建一个新的 delegateQueue。但是如果你自己设置了一个 NSURLSession 的委托队列,请确保你不要在同一个队列中调用你的方法,因为它会阻塞它。

【讨论】:

  • 是的,非常糟糕的建议。根本不是 Objective-C 的方式。
  • @SergiiMartynenkoJr 对于像你这样的人,我已经添加了说明。同样对于命令行实用程序等特定情况,这是一种常见的方法。
  • 一个“像我这样的人”不能默默地按照你的答案移动而不评论它=)
  • 这种方法的问题是你不能保证,dataTask 块将被分派到哪个队列。实际上,它可以被分派到同一个串行队列,getJsonDataFromURL 在该队列上执行
  • 是的,我错过了。实际上,我是快速判断的,真丢脸。对不起。你不能编辑你的答案,所以我可以投票吗?所以不要让我在没有编辑的情况下这样做。添加单词或标点符号就可以了:)
【解决方案3】:

很明显 Method 会在 Block 完成之前返回。因为这是该块的主要目的。

你需要改变这样的东西:

NSDictionary* jsonResponse;

+ (NSDictionary*) getJsonDataFromURL:(NSString *)urlString{

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        self.jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);

        [dataTask resume];

// ad observer here that call method to update your UI
    }];


}

【讨论】:

    【解决方案4】:

    这是“异步调用”的预期行为。它们不应阻塞调用线程,而是在调用完成时执行传递的块。

    只需在block中添加得到结果后必须执行的代码即可。

    所以不是……

    + (NSDictionary*) getJsonDataFromURL:(NSString *)urlString
    {
      __block NSDictionary* jsonResponse;
      NSURLSession *session = [NSURLSession sharedSession];
      NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
      ^(NSData *data, NSURLResponse *response, NSError *error) 
      {
        jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);
      }];
    
      [dataTask resume];
    
      return jsonResponse;
    }
    …
    NSDictionary* jsonResponde = [self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
    // The code that has to be executed is here
    

    ……你这样做:

    + (void) getJsonDataFromURL:(NSString *)urlString
    {
      NSURLSession *session = [NSURLSession sharedSession];
      NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:
      ^(NSData *data, NSURLResponse *response, NSError *error) 
      {
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonResponse);
        // Place the code to be executed here <-----
      }];
    
      [dataTask resume];
    }
    …
    [self getJsonDataFromURL:url]; // BTW: get infringes the naming rules of Objective-C
    // No code here any more
    

    如果-getJsonDataFromURL:执行的代码依赖于调用者,只需将其作为参数传递给方法并在指定位置执行即可。如果您需要帮助,请告诉我。我将为其添加代码。

    另一种解决方案是使用信号量并等待,直到执行完成处理程序。但这会阻塞用户界面,而且不是有意的做法。

    【讨论】:

    • 您正在向对象 self 发送类消息 - 错误。而你的提议是违反Separation of concerns。如果您在程序的不同类中需要此方法,并且每个类在获得响应时都在做不同的事情,您会怎么做? V
    • 1. self 不一定是对实例对象的引用。没有错误。 2.在这种情况下,我会重新阅读我的答案,尤其是:“如果 -getJsonDataFromURL: 执行的代码依赖于调用者,只需将其作为参数传递给方法并在指定位置执行。如果您需要帮助那个,让我知道。我会添加它的代码。”
    • 那么,你认为这个方法只能被Class对象调用吗?从这个问题的上下文来看,这是错误的。根据您的回答的含义-将“要执行的代码”传递给self,那是类对象吗?做什么的?它总是一样的。
    • 其实我是想分离任务,让这个方法只是获取响应json,所以以后可以用这个方法,所以不能把这个方法后面需要做的代码放到block里面.
    • @SergiiMartynenkoJr 我不知道,他为什么这样做。该方法在他的 Q 中一直是类方法。而在您的 A 中 - 哎呀。这不是他的 Q,这是否正确并且不能说出来,因为我们没有足够的信息来回答这个未询问的 Q。请重新阅读 Q。但是,当然,有有几十个示例向self 发送指向类对象的消息。我说几十个?我想说吨。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-07
    • 2012-02-13
    • 1970-01-01
    • 1970-01-01
    • 2019-02-24
    • 1970-01-01
    相关资源
    最近更新 更多