Block underlying structure

Blocks are often encountered in daily development. Common use scenarios are used for interaction between two objects, such as value transfer between VC, network interface callback, etc.

Let’s start with a simple example of the underlying structure of a block:

^{ NSLog(@"hello world"); } ();Copy the code

Compile into C++ code from xcrun — SDK iphoneos clang-arch arm64-rewrite-objc main.m -o main.cpp:

struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; Struct __main_block_impl_0 {struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

Block is an object of type _NSConcreteStackBlock.

Variable to capture

A local variable

  • auto
int num = 1;
void (^block)(void) = ^(){
     NSLog(@"num value is: %d", num);
};

num = 2;
block();
Copy the code

Num value is: 1. The value of num was changed before the block call. Let’s compile the code into C++ to see why:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // The structure is the same as above, with only one more variable of num. int num; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; Static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int num = __cself->num;  // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_5f_h3wcc64x7r72drwvgq5drdvh0000gp_T_main_b7ef04_mi_0, num); } int num = 1; / / block declare void block (*) (void) = ((void (*) ()) & __main_block_impl_0 (__main_block_func_0 (void *), &__main_block_desc_0_DATA, num));Copy the code

In the block declaration, you can see that the value of the local num variable has been passed to the block structure __main_block_IMPL_0, the internal num variable. Therefore, any subsequent changes to the value of num do not affect the values already captured in the block.

  • static
static int num = 1; . // The rest of the code is the sameCopy the code

Num value is: 2. You can see that this num change works. Compile to C++ to see why:

struct __main_block_impl_0 { ...... int *num; . }; Static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int *num = __cself->num; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_5f_h3wcc64x7r72drwvgq5drdvh0000gp_T_main_cede7d_mi_0, (*num)); } void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &num));Copy the code

This time you can see that num is preceded by a * to indicate that it is a pointer type. It is also known from the &num pass that it passes the memory address of the static-modified num. Since num in __main_block_IMPL_0 and num in static refer to a block of memory, the change of num = 2 is valid.

The global variable

C + + code:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5f_h3wcc64x7r72drwvgq5drdvh0000gp_T_main_933c32_mi_0, num);
}
Copy the code

As you can see, instead of taking the value from __main_block_IMPL_0, num is accessed directly. Because num is a global variable, it can be accessed directly.

Local variables auto and static; Summary diagram of capturing global variables:

Block type

There are three types of blocks, all of which inherit from NSBlocks:

  • NSGlobalBlock: Global is used if the auto variable is not accessed. Stored in the data segment.
void (^block)(void) = ^(){
    NSLog(@"hello, world");
};

// __NSGlobalBlock__, NSBlock
NSLog(@"%@, %@", [block class], [[block class] superclass]); 
Copy the code
  • NSStackBlockThe stack that accesses the auto variable is stack. Put it on the stack.
// __NSStackBlock__, NSBlock
NSLog(@"%@,%@", [^(){ NSLog(@"num value is: %d", num); } class],
              [[^(){ NSLog(@"num value is: %d", num); } class] superclass]);
              
Copy the code
  • NSMallocBlockStack calls malloc when copy is called. Store it on the heap.
// __NSMallocBlock__,NSBlock
NSLog(@"%@,%@", [[^(){ NSLog(@"num value is: %d", num); } copy] class],
              [[[^(){ NSLog(@"num value is: %d", num); } copy] class] superclass]);
Copy the code

Block copy operation

In an ARC environment, the compiler automatically copies blocks on the stack to the heap if the type changes from __NSStackBlock__ to __NSMallocBlock__.

  • Block is the return value of the method.
// .h
typedef void (^SomeBlock) (void);
@interface Goods : NSObject

- (SomeBlock)test;

@end
// .m
- (SomeBlock)test {
    int a = 1;
    return ^{
        NSLog(@"test -- %d", a);
    };
}
// main
Goods *g = [Goods new];
NSLog(@"%@", [[g test] class]); // __NSMallocBlock__
Copy the code
  • Block is strongly pointer to.
int num = 1;
void (^block)(void) = ^(){
    NSLog(@"num value is: %d", num);
};
NSLog(@"%@", [block class]); //__NSMallocBlock__
Copy the code
  • The system method takes the parameter usingBlock.
  • As an argument to the GCD method.

Block internally accesses the auto variable of the object type

If the auto variable of the object type is accessed inside the block, copy and dispose functions are added to the __main_block_desc_0 structure to manage the object’s memory. For example:

NSObject *obj = [NSObject new];
void (^block)(void) = ^(){
    NSLog(@" %@", obj);
};
block();
Copy the code

Compiled into C++ code, structure __main_block_desc_0 code is as follows:

static struct __main_block_desc_0 { size_t reserved; size_t Block_size; Void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};Copy the code

If the block is on the stack, it does not strongly reference the variable; If the block is on the heap, it is strongly or weakly referenced based on the variable’s modifier (strong/weak).

When a block is on the heap, it calls its internal copy function, which internally calls the _Block_object_assign function for memory management of object variables.

When a block is removed from the heap, it calls its internal dispose function, which internally calls the _Block_object_dispose function to free the object variable.

__block

Use scenario: Solve the problem that the block cannot modify the value of the auto variable, such as the following code:

NSObject *obj = [NSObject new];
void (^block)(void) = ^(){
    obj = nil;
    NSLog(@" %@", obj);
};
block();
Copy the code

Variable is not assignable (missing __block type specifier). Obj solves this problem by adding a __block.

If __block is not added, the block internally captures the value of obj and creates a memory space on the heap to store it. The outer OBJ is stored on the stack, and the inner OBJ is stored on the heap. Although they have the same name, they are not the same address. So you can’t change the value of obj internally.

Without adding __block to print obj’s memory address:

NSObject *obj = [NSObject new]; NSLog(@"0000 %p", &obj); // 0x7ffeefBff3f8 stack void (^block)(void) = ^(){NSLog(@" 1111% p", &obj); / / 0 heap x10304d6f0}; block(); NSLog(@"2222 %p", &obj); / / 0 x7ffeefbff3f8 stackCopy the code

After adding __block, __block captures the memory address of obj, creates a memory space on the heap to store OBJ, and then changes the variable addresses outside the block to the heap as well. So the internal obJ and the external OBJ point to the same block of memory, so you can change the value of OBj internally.

Add __block to print obj’s memory address:

__block NSObject *obj = [NSObject new]; NSLog(@"0000 %p", &obj); // 0x7ffeefBff3f8 stack void (^block)(void) = ^(){NSLog(@" 1111% p", &obj); / / 0 heap x100507ce8}; block(); NSLog(@"2222 %p", &obj); / / 0 x100507ce8 stackCopy the code

The above illustration:

Note that __block only modifies auto and not static and global variables.

A circular reference

When blocks are used, code irregularities can cause circular references, which can lead to memory leaks. For example:

@interface Goods : NSObject
@property(nonatomic, copy)SomeBlock block;
@property(nonatomic, copy)NSString *name;
@end
Goods *g = [Goods new];
g.block = ^{
    NSLog(@"%@", g.name);
};
Copy the code

Because G strongly references a block, and g is strongly referenced within the block, the circular reference causes G to be unable to be released. The key can be __weak, __unsafe_unretained, and __block. Again, using __weak as an example:

Goods *g = [Goods new];
__weak typeof(g) weakG = g;
g.block = ^{
    NSLog(@"%@", weakG.name);
};
Copy the code

conclusion

  • The local auto variable is value capture; Local static variables are pointer capture; Direct access to global variables.
  • The three types of block, Global, Stack, and Malloc, all inherit from NSBlock.
  • When blocks are return values, strong Pointers, and arguments to certain system methods, the compiler automatically copies them from the stack to the heap.
  • An __block modified auto variable that can be modified inside a block.
  • __weak/__unsafe_unretained/__block is used to solve the recurring reference problem.