📜 ⬆️ ⬇️

Objective-C: how blocks work

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; }; /// A pointer to an instance of a class. typedef struct objc_object *id; 

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 dispatch_block_t * * @abstract * The type of blocks submitted to dispatch queues, which take no arguments * and have no return value. * * @discussion * When not building with Objective-C ARC, a block object allocated on or * copied to the heap must be released with a -[release] message or the * Block_release() function. * * The declaration of a block literal allocates storage on the stack. * Therefore, this is an invalid construct: * <code> * dispatch_block_t block; * if (x) { * block = ^{ printf("true\n"); }; * } else { * block = ^{ printf("false\n"); }; * } * block(); // unsafe!!! * </code> * * What is happening behind the scenes: * <code> * if (x) { * struct Block __tmp_1 = ...; // setup details * block = &__tmp_1; * } else { * struct Block __tmp_2 = ...; // setup details * block = &__tmp_2; * } * </code> * * As the example demonstrates, the address of a stack variable is escaping the * scope in which it is allocated. That is a classic C bug. * * Instead, the block literal must be copied to the heap with the Block_copy() * function or by sending it a -[copy] message. */ 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.
link
So 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 connoisseurs
clang -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.

Source: https://habr.com/ru/post/271255/


All Articles