Objective-C Blocks Under the Hood

written in blocks, ios, objective-c

Recently I was asked to describe the ‘under the hood’ memory management of variable and objects in blocks. I think that in 2013 almost every iOS dev knows the practical usage and effect of putting the __block keyword before the declaration of a variable, but maybe what’s happening for real is not so straightforward.

Heads up:

  • Blocks are created on the stack
  • Blocks can be copied to the heap
  • Blocks have their own private const copies of stack variables (and pointers)
  • Mutable stack variables and pointers must be declared with the __block keyword

If blocks aren’t kept around anywhere, will remain on the stack and will go away when their stack frame returns. While on the stack, a block has no effect on the storage or lifetime of anything it accesses. If blocks need to exist after the stack frame returns, they can be copied to the heap and this action is an explicit operation. This way, a block will gain reference-counting as all objects in Cocoa. When they are copied, they take their captured scope with them, retaining any objects they refer. If a block references a stack variable or pointer, then when the block is initialized it is given its own copy of that variable declared const, so assignments won’t work. When a block is copied, the __block stack variables it reference are copied to the heap and after the copy operation both block on the stack and brand new block on the heap refer to the variables on the heap.

Apple documentation helped me in some ways to make things clear, also this article, but to clearly see what’s going on in memory I arrange a little exercise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void (^function())()
{
    int i1 = 4;
    __block int bi1 = 8;
    NSMutableString *ms1 = [@"mutable string 1" mutableCopy];
    __block NSMutableString *bms1 = [@"__block mutable string 1" mutableCopy];

    MyBlock block = ^(id arg1, id barg1, int iarg1, int biarg1) {
        NSLog(@"*** BLOCK CALLED ***");
        NSLog(@"<%p>: %i", &i1, i1);
        NSLog(@"<%p>: %i", &bi1, bi1);
        NSLog(@"<%p - %p>: %@ [%i]", &ms1, ms1, ms1, [ms1 retainCount]);
        NSLog(@"<%p - %p>: %@ [%i]", &bms1, bms1, bms1, [bms1 retainCount]);
        NSLog(@"<%p>: %i", &iarg1, iarg1);
        NSLog(@"<%p>: %i", &biarg1, biarg1);
        NSLog(@"<%p - %p>: %@ [%i]", &arg1, arg1, arg1, [arg1 retainCount]);
        NSLog(@"<%p - %p>: %@ [%i]", &barg1, barg1, barg1, [barg1 retainCount]);
        NSLog(@"*** BLOCK CALL ENDED ***");
    };

    NSLog(@"<%p>: %i", &i1, i1);
    NSLog(@"<%p>: %i", &bi1, bi1);
    NSLog(@"<%p - %p>: %@ [%i]", &ms1, ms1, ms1, [ms1 retainCount]);
    NSLog(@"<%p - %p>: %@ [%i]", &bms1, bms1, bms1, [bms1 retainCount]);

    // 1st block invocation
    block(ms1, bms1, i1, bi1);

    NSLog(@"*** BLOCK <%p> [%i] ***", block, [block retainCount]);

    MyBlock copiedBlock = [[block copy] autorelease];

    NSLog(@"*** COPIED BLOCK <%p> [%i] ***", copiedBlock, [copiedBlock retainCount]);

    // 2nd block invocation
    copiedBlock(ms1, bms1, i1, bi1);

    i1 = 42;
    bi1 = 108;
    ms1 = [@"mutable string 1 replaced" mutableCopy];
    bms1 = [@"__block mutable string 1 replaced" mutableCopy];

    NSLog(@"<%p>: %i", &i1, i1);
    NSLog(@"<%p>: %i", &bi1, bi1);
    NSLog(@"<%p - %p>: %@ [%i]", &ms1, ms1, ms1, [ms1 retainCount]);
    NSLog(@"<%p - %p>: %@ [%i]", &bms1, bms1, bms1, [bms1 retainCount]);

    // 3rd block invocation
    block(ms1, bms1, i1, bi1);
  
    // 4th block invocation
    copiedBlock(ms1, bms1, i1, bi1);
}

LLDB shows that a block is a nice piece of things.

debugger

The most important thing to note is that __block variables and pointers are treated inside the block as structs that obviously handle the reference to the real value/object.

Ready? Here is the log. With explanation!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<0xbfffdd44>: 4
// stack address, int primitive type (4)

<0xbfffdd40>: 8
// stack address, int primitive type (8)

<0xbfffdd2c - 0x7180120>: mutable string 1 [1]
// pointer (stack) and object (heap) addresses, NSMutableString object ("mutable string 1")

<0xbfffdd20 - 0x7180160>: __block mutable string 1 [1]
// pointer (stack) and object (heap) addresses, NSMutableString object ("__block mutable string 1")

*** BLOCK CALLED ***
<0xbfffdcf4>: 4
// stack address, int primitive type (4), int value copied inside the block at declaration time as const

<0xbfffdd40>: 8
// stack address, int primitive type (8), inside the block there is a reference to it

<0xbfffdcfc - 0x7180120>: mutable string 1 [1]
// pointer (stack, copied in block as const) and object (heap) addresses, NSMutableString object ("mutable string 1") not retainded since the block resides on the stack

<0xbfffdd20 - 0x7180160>: __block mutable string 1 [1]
// pointer (stack, reference to pointer copied in block) and object (heap) addresses, NSMutableString object ("__block mutable string 1") not retained

<0xbfffdc10>: 4
// stack address, int primitive type (4), int arg passed by value inside the block

<0xbfffdc0c>: 8
// stack address, int primitive type (8), int arg passed by value inside the block

<0xbfffdc18 - 0x71b3810>: mutable string 1 [1]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("mutable string 1")

<0xbfffdc14 - 0x71b3850>: __block mutable string 1 [1]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("__block mutable string 1")

*** BLOCK CALL ENDED ***

*** BLOCK <0xbfffdce0> [1] ***
*** COPIED BLOCK <0x759aa90> [1] ***
// block copied to the heap

*** BLOCK CALLED ***
<0x759aaa4>: 4
// heap address, int primitive type (4), int value copied inside the block at declaration (copy) time as const

<0x759aa60>: 8
// heap address, int primitive type (8), inside the block there is a reference to it (int copied to the heap due to [block copy])

<0x759aaac - 0x7180120>: mutable string 1 [2]
// pointer (heap, copied in block) and object (heap) addresses, NSMutableString object ("mutable string 1") retained since this block now lives on the heap

<0x759aa88 - 0x7180160>: __block mutable string 1 [1]
// pointer (heap, reference to pointer copied in block) and object (heap) addresses, NSMutableString object ("__block mutable string 1") not retained

<0xbfffdc10>: 4
// stack address, int primitive type (4), int arg passed by value inside the block

<0xbfffdc0c>: 8
// stack address, int primitive type (8), int arg passed by value inside the block

<0xbfffdc18 - 0x7180120>: mutable string 1 [2]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("mutable string 1")

<0xbfffdc14 - 0x7180160>: __block mutable string 1 [1]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("__block mutable string 1")

*** BLOCK CALL ENDED ***

<0xbfffdd44>: 42
// stack address, int primitive type (4)

<0x759aa60>: 108
// heap address, int primitive type (8) (int copied to the heap due to [block copy])

<0xbfffdd2c - 0x755a580>: mutable string 1 replaced [1]
// pointer (stack) and object (heap) addresses, NSMutableString object ("mutable string 1 replaced")

<0x759aa88 - 0x75595a0>: __block mutable string 1 replaced [1]
// pointer (stack) and object (heap) addresses, NSMutableString object ("__block mutable string 1 replaced")

*** BLOCK CALLED ***
<0xbfffdcf4>: 4
// stack address, int primitive type (4), int value copied inside the block at declaration time

<0x759aa60>: 108
// heap address, int primitive type (8),  inside the block there is a reference to it (int copied to the heap due to [block copy])

<0xbfffdcfc - 0x7180120>: mutable string 1 [2]
// pointer (stack, copied in block as const) and object (heap) addresses, NSMutableString object ("mutable string 1") (retained)

<0x759aa88 - 0x75595a0>: __block mutable string 1 replaced [1]
// pointer (stack, reference to pointer copied in block) and object (heap) addresses, NSMutableString object ("__block mutable string 1 replaced") (not retained)

<0xbfffdc10>: 42
// stack address, int primitive type (4), int arg passed by value inside the block

<0xbfffdc0c>: 108
// stack address, int primitive type (8), int arg passed by value inside the block

<0xbfffdc18 - 0x755a580>: mutable string 1 replaced [1]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("mutable string 1 replaced")

<0xbfffdc14 - 0x75595a0>: __block mutable string 1 replaced [1]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("__block mutable string 1 replaced")

*** BLOCK CALL ENDED ***

*** BLOCK CALLED ***
<0x759aaa4>: 4
// heap address, int primitive type (4), int value copied inside the block at copy time as const

<0x759aa60>: 108
// heap address, int primitive type (8), reference to int value (copied to the heap due to [block copy])

<0x759aaac - 0x7180120>: mutable string 1 [2]
// pointer (heap, copied in block) and object (heap) addresses, NSMutableString object ("mutable string 1")  (retained)

<0x759aa88 - 0x75595a0>: __block mutable string 1 replaced [1]
// pointer (heap, reference to pointer copied in block) and object (heap) addresses, NSMutableString object ("__block mutable string 1 replaced") (not retained)

<0xbfffdc10>: 42
// stack address, int primitive type (4), int arg passed by value inside the block

<0xbfffdc0c>: 108
// stack address, int primitive type (8), int arg passed by value inside the block

<0xbfffdc18 - 0x755a580>: mutable string 1 replaced [1]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("mutable string 1 replaced")

<0xbfffdc14 - 0x75595a0>: __block mutable string 1 replaced [1]
// pointer (stack, arg pointer value used inside the block) and object (heap) addresses, NSMutableString object ("__block mutable string 1 replaced")

*** BLOCK CALL ENDED ***

Blocks are first-class citizens in the Objective-C runtime: they have an ‘isa’ pointer which defines a class through which the Objective-C runtime can access methods and storage. In a non-ARC environment you can mess things up for sure, and cause crashes due to dangling pointers. __block applies only when using the variables in the block, it simply says to the block:

Hey, this pointer or primitive type relies on the stack with its own address. Please refer to this little friend with a new variable on the heap. I mean… refer to the object with double dereference, and don’t retain it.
Thank you, sir.

If at some time after the declaration but before the invocation of the block, the object has been released and deallocated, the execution of the block will cause a crash. __block variables are not retained within the block. In the deep end… it’s all about pointers, references, dereferences and retain count stuff. As expected.


Comments