Search code examples
objective-cobjective-c-blocks

retain assigned values from within block in objective-c


I'm trying to create an app that implements customized tunnel protocols. and having issue with assigning value to variable from block, see following code

@implementation ProfileTableViewController{
     __block NSArray *vpnProfiles;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableView.allowsMultipleSelectionDuringEditing = NO;
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray* newManagers, NSError *error)
     {
         if(error != nil){
             NSLog(@"Load Preferences error: %@", error);
         }else{

             if([newManagers count] > 0)
             {
                 vpnProfiles = newManagers;
                 NSLog(@"vpnProfiles 1st: %ld", [vpnProfiles count]);
             }
         }
     }];

 NSLog(@"vpnProfiles 2nd: %ld", [vpnProfiles count]);
}

the above will print

vpnProfiles 2nd: 0
vpnProfiles 1st: 1

my understanding is that this issue is due to async threads, but i can't seem to find a solution after searching and trying.

anyone has suggestions on how to get newManagers to local variable vpnProfiles?


Solution

  • First there is a misunderstanding in the code about the purpose of the __block qualifier.

    This qualifier only applies to local variables (see The __block Storage Qualifier in the Language Specification for Blocks) as its purpose is to extend the lifetime of local variables, if they are modifiable by blocks, past their usual demise once the method/function/language-block they are defined in exits.

    In the code vpnProfiles is an instance variable, whose lifetime is tied to the lifetime of its owning object instance, and as such __block has no meaning – unfortunately the compiler doesn't warn you but just ignores it. Instance variables can be captured and modified by a block as doing so captures the owning object instance as well, thus usually(†) keeping it alive as it is referenced by the block.

    And now to the main question and remark:

    my understanding is that this issue is due to async threads

    Exactly.

    Your loadAllFromPreferencesWithCompletionHandler: method accepts a completion handler which it will presumably run at some future time after the method it has returned...

    Therefore the NSLog within the block is executed after the NSLog following the call to loadAllFromPreferencesWithCompletionHandler: – as can be seen by the output shown in the question.

    anyone has suggestions on how to get newManagers to local instance variable vpnProfiles?

    It is already being assigned by the statement vpnProfiles = newManagers; – the problem is that the code is checking for the assignment before it has happened.

    but i can't seem to find a solution

    The clue is in the name of your method, the block is a completion handler which will be called at some future time when the newManagers are available. It is within this completion handler that you need to schedule the work to do after that point.

    So, for example, you might do the assignment to vpnProfiles and then use dispatch_async() to schedule a block which calls a method on your ProfileTableViewController instance which reads vpnProfiles and does whatever is needed.

    I'm trying to create an app that implements customized tunnel protocols.

    It is time to further research asynchronous design methods as it is essential for such code. Have fun!

    HTH

    Note

    (†) If the captured object reference is weak the object is not kept alive. To find out more about this research "reference cycles" and "weak self".