OC笔记 - block工作原理 01:捕获外部变量(基本类型)


block底层实现

1 - 我们先看一个简单的 block实现

① 无返回值无参型 block

 1 #import 
 2 int main(int argc, const char * argv[]) {
 3     
 4     @autoreleasepool {
 5         
 6         // 定义 block01
 7         void(^block01)(void) = ^{
 8             
 9             NSLog(@"hello block");
10         };
11         
12         // 回调
13         block01();
14     }
15     
16     return 0;
17     
18 }

② 编译成 C++代码

 1 struct __main_block_impl_0 {
 2   struct __block_impl impl;
 3   struct __main_block_desc_0* Desc;
 4   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
 5     impl.isa = &_NSConcreteStackBlock;
 6     impl.Flags = flags;
 7     impl.FuncPtr = fp;
 8     Desc = desc;
 9   }
10 };
11 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
12 
13 
14             NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_bacace_mi_0);
15         }
16 
17 static struct __main_block_desc_0 {
18   size_t reserved;
19   size_t Block_size;
20 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
21 int main(int argc, const char * argv[]) {
22 
23     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
24 
25         void(*block01)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
26 
27         ((void (*)(__block_impl *))((__block_impl *)block01)->FuncPtr)((__block_impl *)block01);
28     }
29 
30     return 0;
31 
32 }

③ 代码分析

首先我们把 C++代码变成 OC格式

 1 int main(int argc, const char * argv[]) {
 2 
 3     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
 4 
 5         // 指针变量
 6         void(*block01)(void) = &__main_block_impl_0(__main_block_func_0,
 7                                                     &__main_block_desc_0_DATA
 8                                                     );
 9 
10         // 执行 block01内部代码
11         block01->FuncPtr(block01);// 指向函数 __main_block_impl_0
12     }
13 
14     return 0;
15 
16 }

参数 __main_block_func_0

1 // 封装了 block01内部的执行函数
2 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
3 
4             // 这就是 blocl01内部的代码块 NSLog
5             NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_bacace_mi_0);
6         }

参数 __main_block_desc_0

1 static struct __main_block_desc_0 {
2     
3   size_t reserved;   // 0
4   size_t Block_size; // 就是 struct __main_block_impl_0的大小
5 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; // 结构体成员变量赋值

那么这时候回过头来看 __main_block_impl_0函数的功能就一目了然了

 1 struct __main_block_impl_0 {
 2   struct __block_impl impl; // impl里包含了 block信息:大小、类型、内部执行函数等等
 3   struct __main_block_desc_0* Desc; //  block大小
 4     
 5   // C++的构造函数(类似 OC中的构造方法),它返回自身结构体__main_block_impl_0
 6   // flags=0 默认值是 0,就是说这个参数可以不传
 7   __main_block_impl_0(void *fp,
 8                       struct __main_block_desc_0 *desc,
 9                       int flags=0) {
10     impl.isa = &_NSConcreteStackBlock;// block是什么类型
11     impl.Flags = flags;
12     impl.FuncPtr = fp; // __main_block_func_0
13     Desc = desc;       // block的大小
14   }
15 };

最终关系图示如下

2 - 捕获外部变量(基本类型)

① block02内部使用全局变量、全局静态变量、局部变量、局部静态变量

 1 #import 
 2 // 全局变量
 3 int pets = 1000;
 4 // 全局静态变量
 5 static int army = 2000;
 6 
 7 int main(int argc, const char * argv[]) {
 8     
 9     @autoreleasepool {
10         
11         // 修饰变量的有三种 auto(自动变量:值传递)、static(静态变量:指针传递)、register(寄存器)
12         // 自动变量:离开作用域就销毁
13         int age = 10;// 默认是 auto,它只存在局部变量中
14         
15         // 局部静态变量
16         static int height = 30;
17         
18         void(^block02)(void) = ^{
19             
20             NSLog(@"自动变量 is :%d",age);
21             NSLog(@"局部静态变量 is :%d",height);
22             NSLog(@"全局变量 is :%d",pets);
23             NSLog(@"全局静态变量 is :%d",army);
24         };
25         
26         age = 300;
27         height = 500;
28         pets = 20000;
29         army = 30000;
30         block02();
31         // 自动变量 is :10
32         // 局部静态变量 is :500
33         // 全局变量 is :20000
34         // 全局静态变量 is :30000
35     }
36     
37     return 0;
38     
39 }

② 编译成 C++(OC格式)

 1 // 全局变量不会 block被捕获
 2 int pets = 1000;
 3 static int army = 2000;
 4 
 5 struct __main_block_impl_0 {
 6 
 7   struct __block_impl impl;
 8   struct __main_block_desc_0* Desc;
 9   // 局部(静态)变量会被捕获到 block内部
10   int age;     // 局部变量:值访问
11   int *height; // 局部静态变量:地址访问
12   // age(_age), height(_height) C++语法就是赋值的意思 age = _age;height = _height
13   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
14     impl.isa = &_NSConcreteStackBlock;
15     impl.Flags = flags;
16     impl.FuncPtr = fp;
17     Desc = desc;
18   }
19 };
20 
21 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
22 
23   int age = __cself->age;
24   int *height = __cself->height;
25 
26             // 局部变量:捕获值类型
27             NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_0,age);
28             // 局部静态变量:捕获的是地址
29             NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_1,(*height));
30             // 全局(静态)变量:并会不捕获到 block内部,使用时直接取出使用
31             NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_2,pets);
32             NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_3,army);
33         }
34 
35 static struct __main_block_desc_0 {
36   size_t reserved;
37   size_t Block_size;
38 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
39 
40 
41 // main函数
42 int main(int argc, const char * argv[]) {
43 
44     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
45 
46         int age = 10;
47         static int height = 30;
48         // 注意传参的不同
49         // 局部变量 age  局部静态变量 &height
50         void(*block02)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
51 
52         age = 300;
53         height = 500;
54         pets = 20000;
55         army = 30000;
56         block02->FuncPtr(block02);
57     }
58 
59     return 0;
60 
61 }

细节问题

1 - 分析代码,思考其中的三个问题

// - Person.h

1 #import 
2 @interface Person : NSObject
3 
4 @property(nonatomic,copy)NSString *name;
5 -(void)test;
6 @end

// - Person.m

 1 #import "Person.h"
 2 @implementation Person
 3 
 4 -(void)test{
 5     
 6     void(^blockA)(void) = ^{
 7         
 8         // 问题一:block内部会不会捕获 self
 9         NSLog(@"%@",self);
10         
11         // 问题二:block内部捕获的又是什么
12         NSLog(@"%@",_name);
13         
14         // 问题三:block内部捕获的东东又是什么
15         NSLog(@"%@",[self name]);
16     };
17     
18     blockA();
19 }
20 
21 
22 @end

结论:不管是问题一还是问题二、问题三,其 block内部捕获的都是局部变量  Person *self

问题一:这个不难理解,block 内部捕获局部变量  Person *self

问题二:访问实例对象的成员变量 _name,其实质是 block内部捕获的变量是 Person *self,通过 self取出成员变量

问题三:访问实例对象的 name方法,同样地 block内部捕获的依旧是 Person *self,通过 self找到它的方法

注:OC中的方法都默认有两个参数 self和 _cmd,既然是参数,那么 self可以理解成一个局部变量,当然可以被 block捕获

结语

1 - block变量捕获机制