Detailed analysis of block variable capture principle in IOS

Time:2020-11-22

Block overview

Block is a feature of C language level and runtime. Block encapsulates a piece of code logic, which is also enclosed by {}. It is similar to function / function pointer in standard C language. In addition, block C can reference variables in the definition environment. This is very similar to the concept of “closure” in other languages. In IOS, there are many application scenarios of block, such as code encapsulation as parameter passing. This is widely used in dispatching concurrency (blockoperation in operation) and completion asynchronous callback.

  • Block is a data type specially recommended by apple, and its usage scenarios are quite extensive
  • animation
  • Multithreading
  • Set traversal
  • Network request callback
  • The role of block
  • It is used to save a piece of code, which can be called at an appropriate time
  • Functions are similar to functions and methods

Block capturing variables

1: You can capture and not modify variables

  • local variable

2: Variables can be captured and modified

  • global variable
  • Static variable
  • Wei Local variables modified by block

Principle analysis:

1. Why can local variables be captured and can’t be modified


int a = 10;
void (^blcok)() = [^{
 NSLog(@"%d",a);
} copy];
a=20;
blcok(); // log : a = 10

The result should be known to all, but why?

Let’s take a look at the transformation after clang

From the definition of block


void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a)), sel_registerName("copy")); 

Block is implemented through__ ZMX__ blockTest_ block_ impl_ 0 structure is defined by the construction method. Let’s take a look at this structure


struct __ZMX__blockTest_block_impl_0 {
 struct __block_impl impl;
 struct __ZMX__blockTest_block_desc_0* Desc;
 int a;
 __ZMX__blockTest_block_impl_0(void *fp, struct __ZMX__blockTest_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
};

impt:


struct __block_impl {
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
};

Isa: pointer to class

Flags: some logos

Reserved: some variables reserved

Funcptr: function pointer

__ZMX__blockTest_block_desc_0:


static struct __ZMX__blockTest_block_desc_0 {
 size_t reserved;
 size_t Block_size;
} __ZMX__blockTest_block_desc_0_DATA = { 0, sizeof(struct __ZMX__blockTest_block_impl_0)};

Reserved: some variables reserved

Size: memory size

Wei ZMX__ blockTest_ block_ impl_ 0 construction method

We can see that the constructor has four parameters

Void * FP: function pointer
struct __ ZMX__ blockTest_ block_ desc_ 0 * desc: desc structure
int _ a: Variable
Int flags = 0: the ID can not be passed

We simplify the definition of block


void (*blcok)() = ((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a));

As you can see, we have passed a as a parameter in the definition. In other words, our block gets the value of a at the time of definition, and no matter how to modify the value of a later. The values of a obtained in the block are all the values passed in at the time of definition, which leads to the reason why the block can capture local variables but cannot be modified

2.1 global variables can be captured or modified


(void)blockTest
{
 void (^blcok)() = [^{
 NSLog(@"%d",a);
 } copy]; 
 a = 20;
 blcok(); // log : 20 
} 

Let’s take a look at the transformation after clang

I will not repeat the same part. We can see that the constructor defining blcok does not pass in the previous parameter a

We call the nslog function = above__ ZMX__ blockTest_ block_ func_ 0 function


static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_70ee3a_mi_0,a);
 }

Obviously, when we call block, if you have previously modified the value of a, the new value must be printed

2.2 static variables can be captured or modified


 (void)blockTest
{
 static int a = 10;
 void (^blcok)() = [^{
 NSLog(@"%d",a);
 } copy]; 
 a = 20; 
 blcok(); //log : 20 
}

Let’s take a look at the transformation after clang

Through the constructor, we can see that an int is added to the input parameter*_ a. It’s the address of A. Printed functions__ ZMX__ blockTest_ block_ func_ 0 is the same as the operation of getting the value on the same memory address. So, we can not only access a, but also modify a

2.3   __ Block decorated variables can be captured or modified


(void)blockTest
{
 __block int a = 10;
 void (^blcok)() = [^{
 NSLog(@"%d",a);
 } copy]; 
 a = 20; 
 blcok();// log : 20 
}

Let’s take a look at the transformation after clang

Oh! The structure at this time__ ZMX__ blockTest_ block_ impl_ The a of 0 becomes a structure pointer. Curiously, let’s take a look at this structure


struct __Block_byref_a_0 {
 void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
Isa: points to the class pointer
Forwarding: a pointer to address a
Flags: Logo
Size: size
a: Variable

Let’s take a look at our blocktest function again


static void _I_ZMX_blockTest(ZMX * self, SEL _cmd) {
 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
 void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)), sel_registerName("copy"));
 (a.__forwarding->a) = 20;
 ((void (*)(__block_impl *))((__block_impl *)blcok)->FuncPtr)((__block_impl *)blcok);
}

At this point, the variable a becomes one__ Block_ byref_ A_ We can see that the address and value of a are passed in when we initialize


a = 20 -> (a.__forwarding->a) = 20

Again, we modify the value of a by modifying the value on the memory address pointed to by A

Print function


static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {
 __Block_byref_a_0 *a = __cself->a; // bound by ref
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_c9e1ad_mi_0,(a->__forwarding->a));
 }

We first get the value corresponding to the memory address of a captured by block, and then print it out

So we can capture and modify the value of A

summary

The above is the whole content of this article, I hope that the content of this article has a certain reference value for your study or work. If you have any questions, you can leave a message and exchange, thank you for your support to developeppaer.