0

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?

BorisV
  • 683
  • 3
  • 20

3 Answers3

2

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!

CRD
  • 52,522
  • 5
  • 70
  • 86
  • And how about static variables inside blocks? What is their lifetime and visibility? – BorisV Aug 26 '19 at 08:23
  • 1
    @BorisV - see [this answer](https://stackoverflow.com/questions/46402330/static-variable-inside-block/46403146#46403146) – CRD Aug 26 '19 at 09:11
0

First, in your code there is only one block instance created per call of f1, as there is only one ^{ } expression which is only run once. Even if there were multiple blocks, created in a particular call of f1, they would reference the same x variable, since it is a __block variable, so it is captured by reference. Also, copying a block does not create multiple instances of the block -- it simply moves the block from stack to the heap if it is not already on the heap.

It is necessary to copy the block before storing it in the collection, not because it will create multiple instances of the block (it doesn't), but because a block by default starts out on the stack as an optimization. If you will be storing it in a place that will outlive the scope where the block was created, you must "copy" the block which moves it from the stack to the heap (if it isn't there already). (The collection will "retain" the object that it needs to store, but retaining a block doesn't move it from the stack to the heap.) However, recent versions of the Clang compiler, when compiling in ARC, will implicitly add a copy when you pass a variable of block type into a function parameter of non-block object type (e.g. here, -[NSMutableArray addObject:], which takes type id), so you don't have to do it yourself.

newacct
  • 119,665
  • 29
  • 163
  • 224
-1

First, The block store in both storage1 and storage2 is same block instance. Then, block store in storage2 can update i variable.

Second, If you want to have 2 independent block instances. You must call Block_copy function.

Third, When you copy block, variable with __block specifier is same in both block. Impossible copy variable with __block specifier. Final, If you copy block also, 2 block will can update variable i.

According Apple document: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW6

__block variables live in storage that is shared between the lexical scope of the variable and all blocks and block copies declared or created within the variable’s lexical scope. Thus, the storage will survive the destruction of the stack frame if any copies of the blocks declared within the frame survive beyond the end of the frame (for example, by being enqueued somewhere for later execution). Multiple blocks in a given lexical scope can simultaneously use a shared variable.

Thanh Vu
  • 1,599
  • 10
  • 14
  • How to copy correctly? [b0 copy] or Block_copy(b0)? – BorisV Aug 25 '19 at 10:12
  • 1
    Actually, You must bridge like `dispatch_block_t b1 = (__bridge dispatch_block_t)(Block_copy((__bridge const void *)(b0)));` – Thanh Vu Aug 25 '19 at 10:14
  • 3
    @ThanhVu Sorry but `Block_copy()`, or the `copy` method, is only guaranteed to give you a distinct instance of the block object if the original is a stack-optimised block – in which case it will return a standard heap allocated object. Block objects are essentially immutable (e.g. like `NSString` objects) so you don't need distinct instances of them. Also note that all the bridging has no real impact. – CRD Aug 25 '19 at 21:59
  • 2
    @BorisV You should rarely need to copy a block in ARC-enabled copy (passing them to variadic functions is one of the few places, probably due to a compiler "feature"), but if you do need to your first option `[b0 copy]` is the way to go. – CRD Aug 25 '19 at 22:00
  • @CRD I know don’t need distinct instance. But I try answer the question. Thank for your opinion. – Thanh Vu Aug 25 '19 at 23:59
  • `-copy` and `Block_copy()` are identical in functionality. Just use the method. – bbum Aug 26 '19 at 17:10