Memory Segments for Blocks

Memory Segments for Blocks

In the previous sections, we’ve learned that a Block is implemented as an automatic variable of a struct, and the struct is generated for the Block. Also __block variable is implemented as an automatic variable of a struct, and the struct is generated for the __block variable. Because they are implemented as automatic variables of a struct, the instances are created on the stack as shown in Table 5–1.

images

Also, in previous sections, we’ve learned that a Block is an Objective-C object as well. If we see a Block as an object of Objective-C, its class is _NSConcreteStackBlock. Although not covered earlier, there are similar classes to the _NSConcreteStackBlock:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock

At first, you might notice that the _NSConcreteStackBlock class has “stack” in its name, meaning that objects of this class exist on the stack. Also, objects of the _NSConcreteGlobalBlock class are, as the class name “global” shows, stored in the data section as are global variables as shown in Figure 5–3.

Objects of the _NSConcreteMallocBlock class are, as you can guess from the name, stored on the heap, like memory blocks, which are allocated by the “malloc” function.

images

Figure 5–3. Memory segments for Blocks

A Block as NSConcreteGlobalBlock Class Object

The _NSConcreteStackBlock class is used for all the Blocks in the previous examples; that is, all the Blocks are stored on a stack. In the following I explain how the other class types are used.

First, when a Block literal is written where a global variable is, the Block is created as a _NSConcreteGlobalBlock class object. Let’s see an example.

void (^blk)(void) = ^{printf("Global Block\n");};

int main()
{

In this source code, the Block literal is written with a global variable declaration. When it is converted, it becomes similar to the previous example in the section, “Under the Block’s Hood,” except that a member variable “isa” in the struct for the Block is initialized as follows.

impl.isa = &_NSConcreteGlobalBlock;

So, the class of this Block is _NSConcreteGlobalBlock, which means that an instance of a struct for the Block is stored in the data section. Because automatic variables can’t exist where the global variables are declared, capturing never happens. In other words, the member variables of the instance for the Block don’t rely on the execution context. One instance is enough in one application and the instance is stored in the data section as are global variables.

An instance of the struct for a Block is modified only when it captures automatic variables. For instance, in the following source code, a Block is used many times, but the automatic variable is changed and captured in every for-loop.

typedef int (^blk_t)(int);

for (int rate = 0; rate < 10; ++rate) {
    blk_t blk = ^(int count){return rate * count;};
}

The instances of the struct for the Block are different for each for-loop because they capture the automatic variable. But, if the Block doesn’t capture any variables, the instance of the struct for the Block is the same:

typedef int (^blk_t)(int);

for (int rate = 0; rate < 10; ++rate) {
    blk_t blk = ^(int count){return count;};
}

The instance of the struct for the Block is stored in the data section of the program not only when Block is used where there are global variables, but also when a Block literal is inside a function and doesn’t capture any automatic variables.

In the converted source code with clang, _NSConcreteStackBlock class is always used, but the implementation is different. We can summarize it as follows.

  • When a Block literal is written where there are global variables
  • When the syntax in a Block literal doesn’t use any automatic variables to be captured

In these cases, the Block will be a _NSConcreteGlobalBlock class object and is stored in the data section. Any Block created by another kind of Block literal will be an object of the _NSConcreteStackBlock class, and be stored on the stack.

When is the _NSConcreteMallocBlock class used and the Block stored on the heap? This is the exact answer for the questions in the previous section.

  • Why can’t a Block exist beyond a variable scope?
  • Why does a member variable “__forwarding” of a struct for __block variables exist?

A Block, which is stored in the data section like global variables, can be accessed safely via pointers outside any variable scopes. But the other Blocks, which are stored on the stack, will be disposed of after the scope of the Block is left. And __block variables are stored on the stack as well, so the __block variables will be disposed of when the scope is left (Figure 5–4).

images

Figure 5–4. A Block and a __block variable on the stack

To solve this problem, Blocks provides a functionality to copy a Block or a __block variable from the stack to the heap. Next, we learn how a Block is copied to the heap.

Block on the Heap

As we’ve learned, a Block can be copied to the heap. By copying the Block, the copied Block on the heap can exist even after the scope is left as shown inFigure 5–5. Let’s see what tricks there are to having the Block work properly.

images

Figure 5–5. A Block and __block copied from the stack to the heap

The member variable “isa” of the struct for the copied Block on the heap is overwritten so that the Block becomes a _NSConcreteMallocBlock class object.

impl.isa = &_NSConcreteMallocBlock;

Meanwhile, a __block variable must be accessed properly no matter where it is on the stack or the heap. The member variable “__forwarding” in the struct for a __block variable is used for that.

Even if a __block variable has been copied to the heap, the __block variable on the stack is still accessed in some cases. Because the member variable “__forwarding” of the instance on the stack points to the instance on the heap, regardless of where the __block variable is on the stack or heap, it is accessed properly. I explain this again in the section, “Memory Segments for __Block Variables.”

Copying Blocks Automatically

By the way, how do Blocks offer copy functionality? To tell the truth, when ARC-enabled, in many cases the compiler automatically detects and copies the Block from the stack to the heap. Let’s see the next example, which calls a function returning a Block.

typedef int (^blk_t)(int);

blk_t func(int rate)
{
    return ^(int count){return rate * count;};
}

A function returns a Block, which is stored on the stack; that is, when the control flow returns to the caller, the variable scope is left and the Block on the stack is disposed of. It looks problematic. Let’s check how it is converted when ARC is enabled.

blk_t func(int rate)
{
    blk_t tmp = &__func_block_impl_0(
        __func_block_func_0, &__func_block_desc_0_DATA, rate);

    tmp = objc_retainBlock(tmp);

    return objc_autoreleaseReturnValue(tmp);
}

Because ARC is enabled, “blk_ttmp” is same as “blk_t __strong tmp”, which means the variable is qualified with __strong.

If you’d read the source code runtime/objc-arr.mm in the objc4 runtime library, you saw that objc_retainBlock function is equivalent to _Block_copyfunction. So, the above source code is equivalent to:

tmp = _Block_copy(tmp);

return objc_autoreleaseReturnValue(tmp);

Let’s see what happens with comments:

 /*
  * a Block is assigned from a Block literal to a variable 'tmp',
  * which means, the variable has the instance of struct for the Block on the stack.
  * Block_copy function copies the Block from the stack to the heap.
  * After it is copied, its address on the heap is assigned to the variable 'tmp'.
  */

tmp = _Block_copy(tmp);

 /*
  * Then, the Block on the heap is registered to an autoreleasepool as an Objective-C
object.
  * After that, the Block is returned.
  */

return objc_autoreleaseReturnValue(tmp);

This means when the function returns the Block, the compiler automatically copies it to the heap.

Coping Blocks Manually

I’ve stated that “in many cases, the compiler automatically detects.” When it doesn’t detect, you have to copy the Block from the stack to the heap manually. To do that, you use an instance method “copy”. The “copy” method, which we’ve seen many times in Part I, is in the alloc/new/copy/mutableCopy method group. In what situation can’t the compiler detect it?

The answer is:

  • When a Block is passed as an argument for methods or functions

But if the method or the function copies the argument inside, the caller doesn’t need to copy it manually as

  • Cocoa Framework methods, the name of which includes “usingBlock”
  • Grand Central Dispatch API

For example, you don’t need to copy when you use the NSArray instance method “enumerateOb- jectsUsingBlock” or “dispatch_async” function. On the contrary, you need to copy a Block when you pass it to an NSArray class instance method “initWithObjects”. Let’s see that with an example.

- (id) getBlockArray
{
    int val = 10;

    return [[NSArray alloc] initWithObjects:
        ^{NSLog(@"blk0:%d", val);},
        ^{NSLog(@"blk1:%d", val);}, nil];
}

The “getBlockArray” method creates two Blocks on the stack and it passes them to the NSArray class instance method “initWithObjects”. What happens when the Block is executed in the caller after the Block is obtained from the NSArray object?

id obj = getBlockArray();

typedef void (^blk_t)(void);

blk_t blk = (blk_t)[obj objectAtIndex:0];

blk();

The application crashes where “blk()” is executed. In other words, Block execution throws an exception. After returning from the getBlockArray function, the Block on the stack has been destroyed. In this case, unfortunately, the compiler can’t detect whether a copy is needed. Although the compiler could copy all the Blocks every time without detecting if a copy is needed, it consumes too many CPU resources when a Block is copied from the stack to the heap. If a Block on the stack is enough and still copied to the heap, CPU power is just being used for nothing. So, the compiler doesn’t do that. But, instead, sometimes you have to copy a Block manually.

The source code will work after some modifications as follows.

- (id) getBlockArray
{
    int val = 10;

    return [[NSArray alloc] initWithObjects:
        [^{NSLog(@"blk0:%d", val);} copy],
        [^{NSLog(@"blk1:%d", val);} copy], nil];
}

It looks a bit strange, but the “copy” method can be called on a Block literal directly. Of course you can call the “copy” method through a Block type variable.

typedef int (^blk_t)(int);

blk_t blk = ^(int count){return rate * count;};

blk = [blk copy];

By the way, what will happen if the “copy” method is called on a Block on the heap or on a data section? Table 5–2 summarizes them.

images

No matter where the Block is stored, nothing bad happens by calling the copy method. If you are not sure if you have to copy, it is safe just to call “copy” on the Block.

But, is it all right to call “copy” multiple times? With ARC, we can’t call release, however.

Copying Block Multiple Times
blk = [[[[blk copy] copy] copy] copy];

This can be rewritten as follows.

{
    blk_t tmp = [blk copy];
    blk = tmp;
}
{
    blk_t tmp = [blk copy];
    blk = tmp;
}
{
    blk_t tmp = [blk copy];
    blk = tmp;
}
{
    blk_t tmp = [blk copy];
    blk = tmp;
}

Let’s check the source code with comments as shown in Listing 5–6.

Listing 5–6. Copying multiple times with comments

     /*
      * Assume that a Block on the stack was assigned to the variable 'blk'
      */

    blk_t tmp = [blk copy];

     /*
      * A Block on the heap is assigned to the variable 'tmp'.
      * 'tmp' has ownership of it because of its strong reference.
      */

    blk = tmp;

     /*
      * The Block in the variable 'tmp' is assigned to the variable 'blk'.
      * 'blk' has ownership of it because of its strong reference.
      *
      * The Block, which was originally assigned to 'blk',
      * is not affected by this assignment, because it is on the stack.
      *
      * At this moment, the variables 'blk' and 'tmp' have ownership of the Block.
      */

}
     /*
      * Leaving the variable scope 'tmp',
      * and its strong reference disappears and the Block is released.
      *
      * Because the variable 'blk' has ownership of it, the Block isn't disposed of.
      */
{
     /*
      * The variable 'blk' has the Block on the heap.
      * 'blk' has ownership of it because of its strong reference.
      */

    blk_t tmp = [blk copy];

     /*
      * A Block on the heap is assigned to the variable 'tmp'.
      * 'tmp' has ownership of it because of its strong reference.
      */

    blk = tmp;

     /*
      * Because the different value is assigned to the variable 'blk',
      * The strong reference to the Block, which was assigned in the variable 'blk',
disappears
      * and the Block is released.
      *
      * The variable 'tmp' has ownership of the Block,
      * The Block isn't disposed of.
      *
      * The Block in the variable 'tmp' is assigned to the variable 'blk'.
      * The variable 'blk' has ownership because of the strong reference.
      *
      * At this moment, the variables 'blk' and 'tmp' have ownership of the Block.
      */

}
     /*
      * Leaving the variable scope 'tmp',
      * and its strong reference disappears and the Block is released.
      *
      * Because the variable 'blk' has ownership of it, the Block isn't disposed of.
      */

     /*
      * repeating ...
      */

With ARC, it works with no problem.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值