Obj-C:__block变量不保留数据

时间:2014-02-19 19:39:39

标签: objective-c asynchronous objective-c-blocks

我想我可能会遇到一个异步问题,因为我认为我已经解决了这个问题。无论如何,我正在进行一系列的Web服务调用:

//get the client data
__block NSArray* arrClientPAs;
[dataManager getJSONData:strWebService withBlock:^(id results, NSError* error) {            
    if (error) {
        UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Getting Client Data Error!" message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(@"Okay", nil) otherButtonTitles:nil, nil];
        [alert show];
    } else {
        arrClientPAs = results;
    }
 }];

和getJSONData是这样的:

- (void) getJSONData : (NSString*) strQuery withBlock:(void (^)(id, NSError *))completion {
    NSDictionary* dictNetworkStatus = [networkManager checkNetworkConnectivity];
    NetworkStatus networkStatus = [[dictNetworkStatus objectForKey:@"Status"] intValue];

    if (networkStatus != NotReachable) {
        //set up the url for webservice
        NSURL* url = [NSURL URLWithString:strQuery];
        NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];

        //set up the url connection
        __block id results;
        [NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:
         ^(NSURLResponse* response, NSData* jsonData, NSError* error) {
             if (error) {
                 completion(nil, error);
                 return;
             }

            results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error]; 
            completion(results, nil);
         }];            
    } else {
        //not connected to a network - data is going to have to come from coredata
    }
}

在第一个块中,如果我记录arrClientData,我可以看到我期待的数据,但是当我在它之后记录arrClientData时它是nil。我正在关注这个SO线程 - How to return a BOOL with asynchronous request in a method? (Objective-C)和其他几个。

显然,我正在尝试在异步调用之后获取数据。任何帮助,将不胜感激。

3 个答案:

答案 0 :(得分:2)

我认为问题在于“异步”意味着什么。这是一个图表:

Step One
__block result;
Step Two - do something asynchonous, including e.g. setting result
Step Three

这里的事情发生了什么顺序?第三步发生在之前第二步完成。 是异步的意思:它意味着“继续使用这个代码,不要等待异步内容完成。”因此,在第三步发生时,result变量尚未设置为任何内容。

所以,你只是用你的__block result误导了自己。 __block或没有__block,您无法查明之后result 的内容,因为没有“事后”。您的代码已在__block result成立之前完成。这就是异步代码使用 之后运行的回调(例如你的completion块)的原因,因为它是异步代码的顺序部分(附加到)。您可以通过回调将结果向下传递给,但是您无法在块中向上设置向上并期望稍后检索它。

所以,你的整体结构是这样的:

__block NSArray* arrClientPAs; // it's nil
[call getJSONdata] = step one
     [call sendAsynchronousRequest]
          do the block _asynchronously_ = step two, tries to set arrClientPAs somehow
step three! This happens _before_ step two, ...
... and this entire method ends and is torn down ...
... and arrClientPAs is still nil! 

我再说一遍:你不能从异步块传递任何信息UP。你只能去DOWN。您需要异步块来调用某些独立持久对象的某些方法来将结果交给它并告诉它使用该结果(并在主线程上仔细执行,否则会造成严重破坏) 。您不能为此目的使用任何自动变量,例如声明的NSArray变量arrClientPAs;没有自动范围,方法结束,自动变量消失,没有更多代码可以运行。

答案 1 :(得分:0)

调用后检查'error'变量的值:

results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error];

如果'error'不是nil,则在完成块中会出现数据问题。

答案 2 :(得分:0)

您正在混合样式并混淆__block的目的。

注意:当您调用将异步执行的方法时,您正在创建一个新的执行路径,该路径将在未来的某个时刻执行(包括立即执行) )在一些线程上。

getJSONData方法中,您应该使用__block限定变量results,而不应该使用//set up the url connection [NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse* response, NSData* jsonData, NSError* error) { if (error) { completion(nil, error); return; } id results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error]; completion(results, nil); }]; 。该变量仅在块内需要,并应在那里声明:

__block

在块之外声明变量并添加sendAsynchronousRequest只会增加无意义的复杂性。调用results后,在执行请求之前返回,getJSONData的值是块中指定的值。对完成块的调用是在不同的执行路径上执行的,甚至在调用getJSONData之后才会执行。

然而,你的sendAsynchronousRequest方法的正确性是它的模型 - 它需要一个getJSONData自己的完成处理程序将调用的完成块。这是您对arrClientPAs的调用不正确 - 您传递的完成块将结果传递给另一个块或将它们传递给某个对象,而是为它们分配一个局部变量,getJSONData,在电话会议前宣布。这与上面针对arrClientPAs描述的情况相同,并且由于相同的原因将失败 - getJSONData无法“保留数据”但是您正在当前执行路径中读取它之前另一个执行路径已向其写入任何数据。

你可以像- (void) getTheClientData: ... completionHandler:(void (^)(id))handler { ... //get the client data [dataManager getJSONData:strWebService withBlock:^(id results, NSError* error) { if (error) { UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Getting Client Data Error!" message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(@"Okay", nil) otherButtonTitles:nil, nil]; [alert show]; } else { handler(results); // "return" the result to the handler } }]; 一样解决这个问题 - 封闭方法(不包括在你的问题中)可以采用完成块(代码直接输入答案,期待拼写错误!):

getClientData

还有另一种方法。 当且仅当 sendSynchronousRequest:returningResponse:error:未在主线程上执行且您希望其行为是同步的并返回请求的结果时,您可以发出getClientData代替一个异步的。这将阻止正在执行的线程{{1}},直到请求完成。

通常,如果您有一个异步方法,您不能用同步方法替换它但需要同步行为,则可以使用信号量来阻止当前线程,直到异步调用完成。有关如何执行此操作的示例,请参阅this answer

HTH