Obj-C: __block variable not retaining data

时间秒杀一切 提交于 2019-12-20 05:17:13

问题


I think I might have an async problem going on here, which bites cause I thought I had solved it. Anyway, I am making a bunch of web service calls like so:

//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;
    }
 }];

and getJSONData is like so:

- (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
    }
}

In the first block, if I log arrClientData I can see the data that I am expecting but when I log arrClientData after it it is nil. I was following this SO thread - How to return a BOOL with asynchronous request in a method? (Objective-C) and a couple of others.

Obviously I am trying to get the data after the async call is made. Any help would be appreciated.


回答1:


The problem lies, I think, in what "asynchronous" means. Here's a diagram:

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

What order do things happen in here? Step Three happens before Step Two gets finished. That is what asynchronous means: it means, "go right on with this code, don't wait for the asynchronous stuff to finish." So at the time Step Three happens, the result variable has not yet been set to anything.

So, you are just misleading the heck out of yourself with your __block result. __block or no __block, there is no way you are going to find out out what the result is afterwards, because there is no "afterwards". Your code has completed before your __block result is even set. That is why asynchronous code uses a callback (eg. your completion block) which does run afterwards, because it is sequentially part of (appended to) the asynchronous code. You can hand your result downwards through the callback, but you cannot usefully set it upwards from within the block and expect to retrieve it later.

So, your overall structure is like this:

__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! 🌻

I repeat: you cannot pass any information UP out of an asynchronous block. You can only go DOWN. You need your asynchronous block to call some method of some independently persistent object to hand it your result and tell it to use that result (and do it carefully, on the main thread, or you will cause havoc). You cannot use any automatic variable for this purpose, such as your declared NSArray variable arrClientPAs; there is no automatic scope any more, the method is over, the automatic variable is gone, there is no more code to run.




回答2:


Check the value of the 'error 'variable after call:

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

If 'error' isn't nil there is a problem with data which you get in your completion block.




回答3:


You are mixing styles and confusing the purpose of __block.

Note: When you call a method that will be executed asynchronously you are creating a new execution path which will be executed at some point in the future (which includes immediately) on some thread.

In your getJSONData method you use a __block qualified variable, results, when you should not. The variable is only required within the block and should be declared there:

//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);
 }];            

Declaring the variable outside of the block and adding __block just adds pointless complexity. After the call to sendAsynchronousRequest, returns before the request has been performed, the value of results would not be the value assigned in the block. The call to the completion block is performed on a different execution path and probably will not even be executed until after the call to getJSONData has returned.

However what is correct about your getJSONData method is its model - it takes a completion block which sendAsynchronousRequest's own completion handler will call. This is what is incorrect about your call to getJSONData - the completion block you pass does not pass on the results to another block or pass them to some object, but instead assigns them a local variable, arrClientPAs, declared before the call. This is the same situation as described above for getJSONData and will fail for the same reasons - it is not the arrClientPAs fails to "retain the data" but that you are reading it on in the current execution path before another execution path has written any data to it.

You can address this problem the same way getJSONData does - the enclosing method (not included in your question) can take a completion block (code entered directly into answer, expect typos!):

- (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
       }
    }];

There is another approach. If and only if getClientData is not executing on the main thread and you wish its behaviour to be synchronous and to return the result of the request then you can issue a sendSynchronousRequest:returningResponse:error: instead of an asynchronous one. This will block the thread getClientData is executing on until the request completes.

In general if you have an asynchronous method which you cannot replace by a synchronous one but require synchronous behaviour you can use semaphores to block your current thread until the asynchronous call completes. For an example of how to do this see this answer.

HTH



来源:https://stackoverflow.com/questions/21890637/obj-c-block-variable-not-retaining-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!