Search code examples
iosobjective-cmacoscocoaobjective-c-blocks

Objective-C: initialization of blocks stored in collections


I read this article : Storing Blocks in an Array and want to clarify some moments.

Consider next code:

NSMutableArray* storage1;
NSMutableArray* storage2;
-(id)init{
    self=[super init];
    if(self){
        storage1=[NSMutableArray new];
        storage2=[NSMutableArray new];
    }
return self;
}
-(void) f1{
   __block int x=1;

  dispatch_block_t b0=^{
     i++;
     i++;
     sleep_indefinitely
  };
  [storage1 addObject:b0];
  dispatch_async(global_concurrent_thread,b0());
  [storage2 addObject:[storage1 objectAtIndex:0]];
}

The question is: will storage2 contain block with updated value of i or not? Is it necessary to copy block before storing it in collection if I want to have 2 independent instances? And if it is then how the variables with __block specifier are initialized during copying?


Solution

  • The answers to your questions lie in understanding lifetime – every variable and object created in a program has a lifetime which determines how long the variable or object survives. The __block attribute modified the lifetime of the variable it is applied to, potentially increasing it.

    To the gory details (there will be the dead carcases of variables and objects littering the answer ;-)) starting at the beginning with a variable declared within a simple function:

    int answer1(void)
    {
       int x = 21;    // variable x is created with a fixed lifetime of the end of the function
                      // invocation – each function invocation creates a new distinct variable x
       return x * 2;
       // end of the function, x dies
    }
    

    A call to answer1() returns 42. During that call a variable is created and destroyed, after the call it no longer exists. Every call to answer1() creates a new distinct variable, and then destroys it when the invocation returns.

    The lifetime of standard local variables is tied to the function, method or block statement they are created in.

    Dynamically created objects have lifetimes not limited by the function/method/block statement they are created it but live as long as there exists a strong reference to them.

    int answer2(void)
    {
       NSMutableArray *x = [NSMutableArray new]; // variable x is created with a fixed lifetime
                                                 // of the end of the function
                                                 // an NSMutableArray object is created with an
                                                 // indeterminate lifetime – it will live as long
                                                 // as there exists a strong refernece to it
                                                 // a strong reference to this object is stored in x
       [x addObject:@21];
       return [x[0] intValue] * 2;
       // end of the function, x dies
       // as x contained the only strong reference to the array object
       // so that object is now longer wanted and can now be culled
       // – its lifetime is now over as well
    }
    

    A call to answer2() also returns 42. Important: the array object created during the call has died not because the call has returned, the reason for variable x's demise, but because there is no longer any strong reference to it stored anywhere – unwanted it is culled.

    Now let's look at blocks. On creation a block object contains copies of the values in any local variables it references:

    typedef int (^IntBlock)(void);   // for convenience
    
    int answer3(void)
    {
       int x = 20;    // variable x is created with a fixed lifetime of the end of the function
    
       IntBlock b1 = ^{ return x; }; // variable b1 is created with a fixed lifetime of the end
                                     // of the function
                                     // a block object is created with an indeterminate lifetime
                                     // and a strong reference to it is stored in b1
                                     // the block object contains a copy of the *value* in x,
                                     // i.e. 20
    
       x += 2;
    
       IntBlock b2 = ^{ return x; }; // variable b2 is created with a fixed lifetime of the end
                                     // of the function
                                     // a block object is created with an indeterminate lifetime
                                     // and a strong reference to it is stored in b1
                                     // the block object contains a copy of the *value* in x,
                                     // i.e. 22
    
       return b1() + b2();
       // end of function
       // x, b1 and b2 all die
       // as b1 & b2 contained the only strong references to the two block objects
       // they can now be culled – their lifetimes are over
    }
    

    A call to answer3() returns 42 (what a surprise ;-)). During the invocation of answer3() two distinct blocks are created and though the code bodies are the same they contain different values for x.

    And finally we come to __block, the lifetime enhancing attribute for local variables, any local variable blessed with this attribute at birth is not consigned to die at the end of its creating function/method/block statement:

    typedef void (^VoidBlock)(void);
    
    IntBlock answer4(void)
    {
       __block int x = 42;  // variable x is created with a lifetime the longer of:
                            //  * the lifetime of the current invocation of answer4()
                            //  * the lifetime of the longest living block which
                            //    uses x
    
       VoidBlock b1 = ^{ x = x / 2; };     // variable b1 is created with a fixed lifetime of the end
                                           // of the function
                                           // a block object is created with an indeterminate lifetime
                                           // and a strong reference to it is stored in b1
                                           // the block object contains a reference to the *variable* in x
    
       IntBlock b2 = ^{ return x * 2; };   // variable b2 is created with a fixed lifetime of the end
                                           // of the function
                                           // a block object is created with an indeterminate lifetime
                                           // and a strong reference to it is stored in b1
                                           // the block object contains a reference to the *variable* in x
       b1();          // call b1(), alters the value in x
       return b2;     // return a reference to the second block
       // end of function
       // b1 dies
       // as b1 contained the only strong reference to the first block it can now be culled
       // b2 also dies
       // however a reference to the block it referenced is returned by the function, so
       // that block lives
       // the returned block references x so it to lives
    }
    
    void test4(void)
    {
       IntBlock b1 = answer4();            // a reference to a block is returned by answer4() and stored in b1
       NSLog(@"The answer is %d", b1());   // outputs 42
       // end of function
       // b1 dies
       // as b1 contained the onlyt surviving reference to the block returned by answer4()
       // that block may now be culled
       // as that block contains the only surviving reference to the variable x
       // that variable may now be culled
    }
    

    A call to test4() outputs The answer is 42. Note how there is only one x shared by both b1 & b2.

    Further note how the lifetime of local variable x is extended past the invocation of answer4() as it is captured by the block object that is returned. However once the block object's time is up x is culled – like an object its lifetime is dependent on something holding an interest in it.

    Is it a coincidence that the conditions on the lifetime of x are rather similar to that on an object? No, the next example is effectively (i.e. precise details will differ, the visible behaviour is the same) how the compiler handles answer4():

    @interface LifetimeExtenderObject5 : NSObject
    @property int x;
    @end
    @implementation LifetimeExtenderObject5
    @end
    
    IntBlock answer5(void)
    {
       LifetimeExtenderObject5 *leo = [LifetimeExtenderObject5 new];  // variable leo is created with a lifetime of
                                                                      // the end of the function
                                                                      // a LifetimeExtenderObject5 is created with
                                                                      // an indeterminate lifetime and a reference
                                                                      // to it stored in leo
       leo.x = 42;
    
       VoidBlock b1 = ^{ leo.x = leo.x / 2; };      // variable b1 is created with a fixed lifetime of the end
                                                    // of the function
                                                    // a block object is created with an indeterminate lifetime
                                                    // and a strong reference to it is stored in b1
                                                    // the block object contains a copy of the *value* in leo
                                                    // this value is a strong reference to the created
                                                    // LifetimeExtenderObject5, so now there are two strong
                                                    // references to that object
    
       IntBlock b2 = ^{ return leo.x * 2; };        // variable b2 is created with a fixed lifetime of the end
                                                    // of the function
                                                    // a block object is created with an indeterminate lifetime
                                                    // and a strong reference to it is stored in b1
                                                    // the block object contains a copy of the *value* in leo
                                                    // this value is a strong reference to the created
                                                    // LifetimeExtenderObject5, so now there are three strong
                                                    // references to that object
       b1();          // call b1(), alters the value in x
       return b2;     // return a reference to the second block
       // end of function
       // leo dies, but the LifetimeExtenderObject5 object it references has other strong
       // references so it lives
       // b1 dies
       // as b1 contained the only strong reference to the first block it can now be culled
       // that block contained a string reference to created LifetimeExtenderObject5 object,
       // but there are still remaining strong references to that object so it lives
       // b2 also dies
       // however a reference to the block it referenced is returned by the function, so
       // that block lives
       // that block contains a strong reference, the last one, to the created
       // LifetimeExtenderObject5 object, so it still lives.
    }
    
    void test5(void)
    {
       IntBlock b1 = answer5();            // a reference to a block is returned by answer5() and stored in b1
       NSLog(@"The answer is %d", b1());   // outputs 42
       // end of function
       // b1 dies
       // as b1 contained the only surviving reference to the block returned by answer5()
       // that block may now be culled
       // as that block contains the only surviving reference to the created
       // LifetimeExtenderObject5 object that object may now be culled
    }
    

    You asked:

    Is it necessary to copy block before storing it in collection if I want to have 2 independent instances?

    The above hopefully tells you that copying won't get you two independent instances of the captured __block variable. For that you need distinct __block variables to capture, which you get from distinct invocations of the function they are declared in. Every call to answer4() above returns a block which has captured a distinct/"independent instance" of the __block attributed variable x.

    HTH more than it confuses!