【问题标题】:Obj-C: __block variable not retaining dataObj-C:__block 变量不保留数据
【发布时间】:2023-03-28 10:30:02
【问题描述】:

我想我可能在这里遇到了一个异步问题,这很棘手,因为我认为我已经解决了它。无论如何,我正在做一堆这样的网络服务调用:

//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) 和其他几个。

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

【问题讨论】:

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


    【解决方案1】:

    我认为,问题在于“异步”的含义。这是一个图表:

    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 从异步块中传递出去。你只能往下走。你需要你的异步块调用一些独立持久对象的方法来传递你的结果并告诉它使用这个结果(并且在主线程上小心地做,否则你会造成破坏) .您不能为此目的使用 any 自动变量,例如您声明的 NSArray 变量arrClientPAs;不再有自动作用域,方法结束,自动变量消失,没有更多代码可运行。

    【讨论】:

    • 我明白异步是什么意思,我不知道如何解决它。 : D
    • 等一下,我仍在完善我的答案(可能会或可能不会变得更有用):))))
    • @Pruitlgoe 而不是将变量分配给块变量,您应该向对象发送消息以存储数据(当任务完成时)。
    • 是的,说得好,@AminNegm-Awad - 和/或在 completion 处理程序中使用它。
    • 不,因为您的[dataManager getJSONData:strWebService withBlock:^(id results, NSError* error) { 呼叫也是如此。您拨打该电话,您的代码将继续执行并完成。故事结局!您无法通过在块内设置__block arrClientPAs 进行回复。该设置发生在稍后 - 它是异步的!你调用getJSONData 的方法结束了,完成了,死了,消失了,完成了。这是一种前方法!它去见它的制造者了。它正在推高雏菊。 :)))
    【解决方案2】:

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

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

    如果“错误”不为零,则您在完成块中获得的数据有问题。

    【讨论】:

    • 该应用程序最初调用此方法 8 次,所有 8 次错误均为零。另外,我可以看到正确的数据,只是没有将其传递出去。
    【解决方案3】:

    你在混用风格,混淆了__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 的调用的不正确之处-您传递的完成块将结果传递给另一个块或将它们传递给某个对象,而是为它们分配了一个局部变量,@ 987654334@,在通话前声明。这与上面针对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 正在执行的线程,直到请求完成。

    一般来说,如果您有一个无法用同步方法替换但需要同步行为的异步方法,您可以使用 semaphores 来阻塞当前线程,直到异步调用完成。有关如何执行此操作的示例,请参阅 this answer

    HTH

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-11
      • 1970-01-01
      • 1970-01-01
      • 2015-11-09
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      • 2021-11-17
      相关资源
      最近更新 更多