In this article, I will discuss the location of the blocks (__NSStackBlock __ / __ NSGlobalBlock __ / __ NSMallocBlock__), how variables are captured and how they are related to what the block is compiled into.
At the moment, the use of blocks in Objective-C begins almost from the first days of learning this language. But in most cases, developers do not think about how the blocks work inside. There will be no magic here, I’ll just tell you more about it.
Let's start from the beginning, what a block looks like in Objective-C
')
For which blocks are used, I will not paint, it is not about that, so let's immediately consider interesting points in practice.
What is a block? First of all, a block is an object.
id thisIsBlock = ^{ };
For understanding, consider what an object is.
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; };
And the block has a method
- (Class)class
which isa returns
We take advantage of what we know now, and see which classes the block has in what situation
__NSStackBlock__ int foo = 3; Class class = [^{ int foo1 = foo + 1; } class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22: 30: 21.054 block_testing [99727: 13189641] __NSStackBlock__
__NSMallocBlock__ int foo = 3; Class class = [[^{ int foo1 = foo + 1; } copy] class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22: 33: 45.026 block_testing [99735: 13190778] __NSMallocBlock__
__NSGlobalBlock__ Class class = [^{ } class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22: 34: 49.645 block_testing [99743: 13191389] __NSGlobalBlock__
But consider another option __NSMallocBlock__
ARC int foo = 3; id thisIsBlock = ^{ int foo1 = foo + 1; }; Class class = [thisIsBlock class]; NSLog(@"%@", NSStringFromClass(class));
2015-11-29 22: 37: 27.638 block_testing [99751: 13192462] __NSMallocBlock__
As you can see, if the block does not capture external variables, then we get __NSGlobalBlock__
Experiments Class class = [[^{ } copy] class]; NSLog(@"%@", NSStringFromClass(class));
id thisIsBlock = ^{ }; Class class = [thisIsBlock class]; NSLog(@"%@", NSStringFromClass(class));
__NSGlobalBlock__
If the block captures external variables, then the __NSStackBlock__ block (on the stack). However, if you send a 'copy' to the block, the block will be copied to the heap (__NSMallocBlock__).
ARC helps us with this, and when assigning a block to a variable (__strong), the block will be copied into a heap, which can be seen in the example above. In general, once again we say ARC thanks, because in the MRC we could get extremely nasty bugs
object.h typedef void (^dispatch_block_t)(void);
For what, then, are they labeled as 'copy'?
It should be noted. It would be the case that it would be the case. For more information, see Blocks Programming Topics.
linkSo go ahead and write property copy for blocks.
Also consider another small example about __NSStackBlock__
What happens if I pass __NSStackBlock__ to a function? typedef void(^EmptyBlock)(); void fooFunc(EmptyBlock block) { Class class = [block class]; NSLog(@"%@", NSStringFromClass(class)); } int main(int argc, const char * argv[]) { @autoreleasepool { int foo = 1; fooFunc(^{ int foo1 = foo + 1; }); } return 0; }
2015-11-29 22: 52: 16.905 block_testing [99800: 13197825] __NSStackBlock__
Why do you need __NSGlobalBlock__About __NSStackBlock__ and __NSMallocBlock__ was a lot of information, what is the use of __NSGlobalBlock__? Inside such a block, there is no capture of external variables, which means no violations can occur due to memory visibility, as when using __NSStackBlock__. And you do not need to first create a block on the stack, then, if necessary, transfer it to a heap, which means that you can resolve this situation much easier.
The article said so many times about the capture of external variables, but we still have not said how this happens. The
clang documentation will help us with this. We will not paint it completely, we will select only the necessary ideas.
The block turns into a structure, and the grips turn into fields.
This is easy to verify in practice.
footnote for connoisseursclang -rewrite-objc -ObjC main.m -o out.cpp
and you can see the whole process in C ++ code
MyObject *myObject = [[MyObject alloc] init]; NSLog(@"object %p, ptr myObject %p", myObject, &myObject); ^{ NSLog(@"object %p, ptr myObject %p", myObject, &myObject); }();
2015-11-29 23: 12: 37.297 block_testing [99850: 13203592] object 0x100111e10, ptr myObject 0x7fff5fbff798
2015-11-29 23: 12: 37.298 block_testing [99850: 13203592] object 0x100111e10, ptr myObject 0x7fff5fbff790
As you can see, the pointer myObject outside and inside the block points to the same memory area, but the pointer itself is different.
A block creates constant local variables and pointers of what it captures inside. That when using objects will increase the reference count, according to the usual logic for ARC (when copying a block to a heap).
However, when using __block, inout will occur, so the reference count will not increase (however, you should not use it always and everywhere, const is a good word).
To fix, consider a small example.
code #import <Foundation/Foundation.h> typedef NSInteger (^IncrementBlock)(); IncrementBlock createIncrementBlock(const NSInteger start, const NSInteger incrementValue) { __block NSInteger acc = start; return ^NSInteger{ acc += incrementValue; return acc; }; } int main(int argc, const char * argv[]) { @autoreleasepool { IncrementBlock incrementBlock = createIncrementBlock(0, 2); NSLog(@"%ld", incrementBlock()); NSLog(@"%ld", incrementBlock()); NSLog(@"%ld", incrementBlock()); IncrementBlock incrementBlock1 = createIncrementBlock(0, 2); NSLog(@"%ld", incrementBlock1()); NSLog(@"%ld", incrementBlock1()); NSLog(@"%ld", incrementBlock1()); } return 0; }
2015-11-29 23: 31: 24.027 block_testing [99910: 13209611] 2
2015-11-29 23: 31: 24.028 block_testing [99910: 13209611] 4
2015-11-29 23: 31: 24.028 block_testing [99910: 13209611] 6
2015-11-29 23: 31: 24.028 block_testing [99910: 13209611] 2
2015-11-29 23: 31: 24.028 block_testing [99910: 13209611] 4
2015-11-29 23: 31: 24.029 block_testing [99910: 13209611] 6
The IncrementBlock block was turned by the compiler into a structure, when the block returned from a function, the current region was copied, and thus we got a structure that has a field in which the battery is stored. And for each call to the createIncrementBlock function, we get a new instance.
I will also stop focusing on the case of using self inside a block (
what self is ). After reading the article, it should be clear that using self inside __NSMallocBlock__ will increase the reference count, but does not mean a retain cycle at all. retain cycle is when an object holds a block, and a block holds an object that holds a block ...
Paranoia everywhere use __weak __strong is not needed, for us a block is an object. Just an object that can be saved, transferred, used, with which the usual rules of memory management apply.