In the last post, I mentioned that __block
variable (here we named it block
byref) will be retained if multiple blocks referenced it. Here are some sample
code to show how runtime deals with reference counts.
In order to move the __block
variable to the heap, the compiler must rewrite
access to such a variable to be indirect through the structures forwarding
pointer. For example:
1 2 |
|
would be rewritten to be:
1 2 3 4 5 6 7 8 9 |
|
Print runtime information
As long as we know how block byref is structured, we can access the memory and
dump it with internal function _Block_byref_dump
.
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 |
|
The execution result is
$ ./more_curious
Before local block:
byref data block 0x7fff6e8034f0 contents:
forwarding: 0x7fff6e8034f0
flags: 0x0
size: 32
After local block generated:
byref data block 0x7fff6e8034f0 contents:
forwarding: 0x7fff6e8034f0
flags: 0x0
size: 32
After first block copy:
byref data block 0x7fc191c13f60 contents:
forwarding: 0x7fc191c13f60
flags: 0x1000004
size: 32
After second block copy:
byref data block 0x7fc191c13f60 contents:
forwarding: 0x7fc191c13f60
flags: 0x1000006
size: 32
Execute block:
byref data block 0x7fc191c13f60 contents:
forwarding: 0x7fc191c13f60
flags: 0x1000004
size: 32
x is 2, &x is 0x7fc191c13f78
Execute block:
byref data block 0x7fc191c13f60 contents:
forwarding: 0x7fc191c13f60
flags: 0x1000002
size: 32
x is 3, &x is 0x7fc191c13f78
What does it mean?
We can find some interesting things in this log:
- Block byref flags and address doesn’t change until first copy.
- After copy, the flag becomes
0x1000004
. There’s a(1 << 24)
flag in the front. - Block releases does decrease flag number in times of 2.
The (1 << 24)
flag (the number one in 0x100xxxxx
) means BLOCK_NEEDS_FREE
in this enum
:
1 2 3 4 5 6 7 8 9 |
|
So, flag changes until first copy makes sense, because block byref doesn’t need free until it is copied to heap.
The reference count is actually taken out from flags like so:
1
|
|
I didn’t find out why reference count is in times of two. The actual code that increase and decrease reference count is this:
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 |
|
OSAtomicCompareAndSwapInt
is a function that can change value of a int
thread and multiprocessor safe.
Conclusion
Block seems magical at the first seen. With block we no longer have to do the
function pointer + struct cast + void* tricks. Block automatically captures
variables for us, and we can use __block
storage qualifier to declare mutable
ones. Behind the scene is really cool hack to make all this happen. However, it
not quite easy to debug blocks and byrefs. We’d need to write some helper
functions for gdb
or lldb
. These will be discussed in my next post.