1. Block类型

在日常开发中,Block分为三种类型:

  • GlobalBlock

    • 位于全局区

    • Block内部不能捕获外部变量,或只使用静态变量或全局变量

  • MallocBlock

    • 位于堆区

    • Block内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量

  • StackBlock

    • 位于栈区

    • MallocBlock一样,可以在内部使用局部变量或OC属性。但不能赋值给强引用或Copy修饰的变量

1.1 GlobalBlock

全局Block,不能捕获外部变量,但可以使用静态变量或全局变量

  1. int age;
  2. - (void)viewDidLoad {
  3. [super viewDidLoad];
  4. static int number;
  5. void (^block)(void) = ^{
  6. int a = 0;
  7. NSLog(@"%d - %d - %d", number, age, a);
  8. };
  9. NSLog(@"%@",block);
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. <__NSGlobalBlock__: 0x1047900f8>
  • 使用静态变量、全局变量、Block内部声明的变量,都没有问题

1.2 MallocBlock

堆区Block,内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. int number = 0;
  4. void (^block)(void) = ^{
  5. NSLog(@"%d", number);
  6. };
  7. NSLog(@"%@",block);
  8. }
  9. -------------------------
  10. //输出以下内容:
  11. <__NSMallocBlock__: 0x281487930>
  • 对外部的number变量进行捕获
  • 赋值给强引用的block
  • 其中block持有的是堆区Block的内存地址

1.3 StackBlock

栈区Block,和堆区Block的使用大致相同,区别在于不能赋值给强引用或Copy修饰的变量

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. int number = 0;
  4. void (^__weak block)(void) = ^{
  5. NSLog(@"%d", number);
  6. };
  7. NSLog(@"%@",block);
  8. }
  9. -------------------------
  10. //输出以下内容:
  11. <__NSStackBlock__: 0x16b5e0f68>
  • 同样对外部的number变量进行捕获
  • 和堆区Block的区别:赋值给弱引用的block

2. Block案例

2.1 Block的内存拷贝

  1. - (void)blockDemo{
  2. int a = 0;
  3. void(^__weak weakBlock)(void) = ^{
  4. NSLog(@"a:%d", a);
  5. };
  6. struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
  7. id __strong strongBlock = weakBlock;
  8. NSLog(@"weakBlock:%@",weakBlock);
  9. NSLog(@"strongBlock:%@",strongBlock);
  10. blc->invoke = nil;
  11. void(^strongBlock1)(void) = strongBlock;
  12. NSLog(@"strongBlock1:%@",strongBlock1);
  13. strongBlock1();
  14. }
  15. -------------------------
  16. //输出以下内容:
  17. weakBlock:<__NSStackBlock__: 0x16f6b4f48>
  18. strongBlock:<__NSStackBlock__: 0x16f6b4f48>
  19. strongBlock1:<__NSMallocBlock__: 0x283a48ff0>
  20. 程序闪退,坏地址访问(EXC_BAD_ACCESS

代码解读:

  • 第一步,weakBlock为栈区Block

  • 第二步,按Block的底层源码,自定义_LGBlock结构体。只要内存结构一致,即可将Block桥接为自定义对象

  • 第三步,Block的本质是结构体,将结构体首地址赋值给__strong修饰的对象

  • 第四步,将结构体的invoke置空,即:Block的函数指针

  • 第五步,将strongBlock赋值给强引用的strongBlock1,然后对其进行调用

闪退原因:

  • 在第三步中,将结构体首地址赋值给对象,二者指向相同内存空间

  • 在第四步中,将结构体的invoke置空,修改的是同一片内存空间

  • 在第五步中,将invoke置空后的Block赋值给strongBlock1,调用时坏地址访问,程序闪退

修改案例:

  1. - (void)blockDemo{
  2. int a = 0;
  3. void(^__weak weakBlock)(void) = ^{
  4. NSLog(@"a:%d", a);
  5. };
  6. struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
  7. void(^strongBlock)(void) = weakBlock;
  8. NSLog(@"weakBlock:%@",weakBlock);
  9. NSLog(@"strongBlock:%@",strongBlock);
  10. blc->invoke = nil;
  11. strongBlock();
  12. }
  13. -------------------------
  14. //输出以下内容:
  15. weakBlock:<__NSStackBlock__: 0x16d220f48>
  16. strongBlock:<__NSMallocBlock__: 0x2829d2d60>
  17. 程序闪退,坏地址访问(EXC_BAD_ACCESS
  • 先将栈区Block赋值给强引用的Block,然后将invoke置空,但依然闪退

闪退原因:Block的赋值只是进行了浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向对象的指针,但两个指针依然指向同一个对象
image.png

解决办法,必须在invoke置空之前,将Block进行深拷贝

  1. - (void)blockDemo{
  2. int a = 0;
  3. void(^__weak weakBlock)(void) = ^{
  4. NSLog(@"a:%d", a);
  5. };
  6. struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
  7. void(^strongBlock)(void) = [weakBlock copy];
  8. NSLog(@"weakBlock:%@",weakBlock);
  9. NSLog(@"strongBlock:%@",strongBlock);
  10. blc->invoke = nil;
  11. NSLog(@"invoke置空");
  12. strongBlock();
  13. }
  14. -------------------------
  15. //输出以下内容:
  16. weakBlock:<__NSStackBlock__: 0x16d3acf48>
  17. strongBlock:<__NSMallocBlock__: 0x28029adf0>
  18. invoke置空
  19. a0

使用lldb,观察两个Blockinvoke置空前后的变化
image.png

  • 深拷贝相当于将对象进行复制,产生一个新的对象。并且会递归复制每个指针类型的实例变量,直到两个对象没有任何公共的部分

所以,栈区Blockinvoke置空,并不影响堆区Block的调用

2.2 对外部变量的引用计数处理

  1. - (void)blockDemo{
  2. NSObject *objc = [NSObject new];
  3. NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
  4. void(^strongBlock)(void) = ^{
  5. NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
  6. };
  7. strongBlock();
  8. void(^__weak weakBlock)(void) = ^{
  9. NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
  10. };
  11. weakBlock();
  12. void(^mallocBlock)(void) = [weakBlock copy];
  13. mallocBlock();
  14. }
  15. //输出以下内容:
  16. 1
  17. 3
  18. 4
  19. 5

代码解读:

  • 第一步,objc初始化后的打印1,没有任何问题

  • 第二步,在strongBlock中打印3,拆分成两步进行分析

1、外部objc变量,被栈区Block捕获,引用计数+1

2、栈区Block赋值给强引用的strongBlock,将栈区Block拷贝到堆区,底层进行深拷贝,引用计数也会+1

  • 第三步,赋值给弱引用的weakBlock,属于栈区Block,仅对外部objc变量进行捕获,引用计数+1

  • 第四步,将栈区Block调用copy方法,赋值给mallocBlock,仅对栈区Block进行了深拷贝,引用计数+1

其实第三步和第四步,等同于第二步的分解,所以打印结果:1、3、4、5

2.3 堆栈Block的释放

  1. - (void)blockDemo{
  2. int a = 10;
  3. void(^__weak weakBlock)(void) = nil;
  4. {
  5. void(^strongBlock)(void) = ^{
  6. NSLog(@"a:%i", a);
  7. };
  8. weakBlock = strongBlock;
  9. NSLog(@"weakBlock:%@",weakBlock);
  10. NSLog(@"strongBlock:%@",strongBlock);
  11. }
  12. weakBlock();
  13. }
  14. -------------------------
  15. //输出以下内容:
  16. weakBlock:<__NSMallocBlock__: 0x280d7cba0>
  17. strongBlock:<__NSMallocBlock__: 0x280d7cba0>
  18. 程序闪退,坏地址访问(EXC_BAD_ACCESS

代码解读:

  • 第一步,weakBlock使用__weak修饰,赋值为nil

  • 第二步,定义代码块,实现一个堆区Block

  • 第三步,将堆区Block赋值给代码块外面的weakBlock

  • 第四步,在代码块执行完毕后,调用weakBlock

闪退原因:

  • 在第三步中,堆区Block赋值__weak修饰的weakBlock,相当于映射关系

  • 在第四步中,当代码块执行完毕,strongBlock由于是堆区Block,出了代码块就会被释放。作为映射的weakBlock,自然也会被置为nil。此时对其进行调用,出现坏地址访问,程序闪退

修改案例:

  1. - (void)blockDemo{
  2. int a = 10;
  3. void(^__weak weakBlock)(void) = nil;
  4. {
  5. void(^__weak strongBlock)(void) = ^{
  6. NSLog(@"a:%i", a);
  7. };
  8. weakBlock = strongBlock;
  9. NSLog(@"weakBlock:%@",weakBlock);
  10. NSLog(@"strongBlock:%@",strongBlock);
  11. }
  12. weakBlock();
  13. }
  14. -------------------------
  15. //输出以下内容:
  16. weakBlock:<__NSStackBlock__: 0x16f6c4f40>
  17. strongBlock:<__NSStackBlock__: 0x16f6c4f40>
  18. a10
  • strongBlock使用__weak修饰,即可正常打印

strongBlock使用__weak修饰后,成为栈区Block。将其赋值给__weak修饰的weakBlock,此时依然是栈区Block。栈区Block的生命周期与代码块无关,依赖于函数栈帧,所以可以正常打印

3. Block拷⻉到堆区

如果Block为全局Block,使用任何方式都不会拷贝到堆区,即使手动copy也没用,它依然是全局Block

  1. - (void)blockCopy {
  2. void (^block)(void) = ^{
  3. NSLog(@"全局Block");
  4. };
  5. NSLog(@"%@", block);
  6. NSLog(@"%@", [block copy]);
  7. }
  8. -------------------------
  9. //输出以下内容:
  10. <__NSGlobalBlock__: 0x1009e00f8>
  11. <__NSGlobalBlock__: 0x1009e00f8>

除此之外,以下四种操作,系统会将Block复制到堆上:

  • 手动Copy

  • Block作为返回值

  • 被强引用或Copy修饰

  • 系统API包含usingBlock

3.1 Block作为返回值

由于栈区Block所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈区copy到堆区。例如:当Block作为函数返回值的时候,肯定会copy到堆区

3.2 系统API包含usingBlock

Block为函数参数时,需要将其手动copy到堆区。但系统API我们不需要处理,比如GCD中携带的usingBlock方法。其他自定义方法传递Block为参数时,都需要进行手动copy

4. 循环引用

对比以下两种Block代码:

  1. self.name = @"lg_cooci";
  2. //Block1
  3. self.block = ^{
  4. NSLog(@"%@",self.name);
  5. };
  6. //Block2
  7. [UIView animateWithDuration:1 animations:^{
  8. NSLog(@"%@",self.name);
  9. }];
  • Block1会出现循环引用,因为blockself持有,而block中使用self,所以self又被block持有。这种相互持有的情况下,就会出现循环引用

  • Block2不会出现循环引用,因为block的持有者是UIView,和self无关,不会出现相互持有的情况,所以不会循环引用

对象正常释放的过程:
image.png

  • A持有BBretainCount进行+1
  • A触发dealloc时,会给B放信号,BretainCount进行-1。此时B的retainCount如果为0,就会调用dealloc,正常释放

对象循环引用的过程:
image.png

  • AB相互持有时,Adealloc无法触发,因为A要等B放信号才能对retainCount进行-1
  • 但是Bdealloc也无法触发,因为B也在等待A的信号。此时AB都在等待对方的释放,最终出现循环引用

避免循环引用的方式:

  • weak-strong-dance

  • Block中强行切断持有者

  • 将持有者作为Block参数进行传递和使用

4.1 weak-strong-dance

  1. __weak typeof(self) weakSelf = self;
  2. self.block = ^{
  3. __strong typeof(self) strongSelf = weakSelf;
  4. NSLog(@"%@",strongSelf.name);
  5. };
  • self赋值给__weak修饰的weakSelf,此时weakSelf属于self的映射,指向同一片内存空间,并且self的引用计数不会发生变化
  • Block中将weakSelf赋值给__strong修饰的strongSelf,避免self提前释放导致访问为nil的情况
  • 因为strongSelf为临时变量,在Block作用域结束后,即可自动释放,因此不会循环引用

4.2 Block中强行切断持有者

  1. __block ViewController *vc = self;
  2. self.block = ^{
  3. NSLog(@"%@",vc.name);
  4. vc = nil;
  5. };
  6. self.block();
  • 使用__block修饰对象,否则vc无法改变,也就是说无法置为nil
  • Block中使用结束,手动将对象置为nil。相当于手动切断持有关系,可以避免循环引用
  • 缺陷:这种方式Block必须调用,否则将无法手动切断持有关系,selfblock都无法释放,最终出现循环引用

4.3 将持有者作为Block参数进行传递和使用

  1. self.vcBlock = ^(ViewController *vc){
  2. NSLog(@"%@",vc.name);
  3. };
  4. self.vcBlock(self);
  • self作为参数,提供给Block内部使用。当Block执行结束,vc会自动释放,然后相互持有关系的切断,self也会释放
  • 这种方式,self的是否依赖于Block的执行结束。如果Block中有延迟执行的代码,self的释放也会延迟

4.4 面试题

以下几个案例,是否会出现循环引用?

案例1:

  1. static ViewController *staticSelf_;
  2. - (void)blockWeak_static {
  3. __weak typeof(self) weakSelf = self;
  4. staticSelf_ = weakSelf;
  5. }
  6. [self blockWeak_static];
  • 会出现循环引用
  • self赋值__weak修饰的对象,它们属于映射关系,指向同一片内存空间。当weakSelf赋值全局静态变量,staticSelf_在程序运行过程中不会主动释放,它会持续持有self,所以self也无法释放

案例2:

  1. - (void)block_weak_strong {
  2. __weak typeof(self) weakSelf = self;
  3. self.doWork = ^{
  4. __strong typeof(self) strongSelf = weakSelf;
  5. weakSelf.doStudent = ^{
  6. NSLog(@"%@", strongSelf);
  7. };
  8. weakSelf.doStudent();
  9. };
  10. self.doWork();
  11. }
  12. [self block_weak_strong];
  • 会出现循环引用
  • doWork内部,strongSelf持有的是self。虽然strongSelf是临时变量,但在doStudent中又被持有,导致引用计数+1。在doWork执行完毕后引用计数-1,但doStudent中的持有还存在,所以会出现循环引用

5. cpp分析

5.1 Block本质

创建block.c文件,写入以下代码:

  1. #include "stdio.h"
  2. int main(){
  3. void(^block)(void) = ^{
  4. printf("LG");
  5. };
  6. block();
  7. return 0;
  8. }
  • 使用.c文件,生成的cpp代码更加干净简洁

使用xcrun命令,生成block.cpp文件

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp

打开block.cpp文件,找到main函数

  1. int main(){
  2. void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  3. ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  4. return 0;
  5. }
  6. -------------------------
  7. //为了方便阅读,剔除强转代码
  8. int main(){
  9. void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
  10. block->FuncPtr(block);
  11. return 0;
  12. }
  • 只生成两行代码:
    • 调用__main_block_impl_0函数,传入两个参数,取地址并赋值block
    • 调用blockFuncPtr函数,传入block

找到__main_block_impl_0的定义:

  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. //参数1
  12. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  13. printf("LG");
  14. }
  15. //参数2
  16. static struct __main_block_desc_0 {
  17. size_t reserved;
  18. size_t Block_size;
  19. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  • 不难看出,Block的本质是结构体,main函数中调用的是结构体的构造函数

  • 结构体中包含两个成员变量

    • __block_impl结构体类型的impl

    • __main_block_desc_0结构体指针类型的Desc

  • 构造函数中生成了flags等于0的默认值,赋值implFlags

  • 参数fpBlock代码块的函数指针,赋值implFuncPtr

  • 参数desc赋值给成员变量Desc

  • 所以main函数中,代码block->FuncPtr(block)就是在对Block进行调用

  • 由此可见,当Block仅定义不调用执行,不会触发Block中的代码块

5.2 捕获外界变量

打开block.c文件,改为以下代码:

  1. #include "stdio.h"
  2. int main(){
  3. int a = 18;
  4. void(^block)(void) = ^{
  5. printf("LG - %d",a);
  6. };
  7. block();
  8. return 0;
  9. }

生成block.cpp文件,找到main函数

  1. int main(){
  2. int a = 18;
  3. void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
  4. block->FuncPtr(block);
  5. return 0;
  6. }
  • 代码发生了改变,之前__main_block_impl_0函数的入参变成三个
  • 增加的第三个参数为外界变量a

找到__main_block_impl_0的定义:

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. int a;
  5. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
  6. impl.isa = &_NSConcreteStackBlock;
  7. impl.Flags = flags;
  8. impl.FuncPtr = fp;
  9. Desc = desc;
  10. }
  11. };
  12. //参数1
  13. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  14. int a = __cself->a; // bound by copy
  15. printf("LG - %d",a);
  16. }
  17. //参数2
  18. static struct __main_block_desc_0 {
  19. size_t reserved;
  20. size_t Block_size;
  21. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  • 结构体中的成员变量也发生了变化,当捕获外界变量时,在结构体内部,会生成相应的成员变量用来存储

  • 成员变量a通过结构体的构造函数赋值:a(_a)

  • main函数中调用Bolck,由于捕获外界变量,此时传入的FuncPtr中的block发挥作用

    • block为自身结构体的指针,将block中的成员变量a赋值给临时变量a,然后对其打印

    • 临时变量a__cself->a的值相同,但地址不同

    • 由于a是值拷贝,Bolck的代码块中不能对a的值进行改变,会造成编译器的代码歧义。所以,此时的a是只读的

  • 捕获外界变量并赋值强引用变量,本该是堆区Block,但结构体中implisa赋值为&_NSConcreteStackBlock,标记为栈区Block。因为在编译时,无法开辟内存空间,所以暂且标记为StackBlock。在运行时,会根据情况将Block拷贝到堆区,然后生成MallocBlock

5.3 __block的作用

打开block.c文件,改为以下代码:

  1. #include "stdio.h"
  2. int main(){
  3. __block int a = 18;
  4. void(^block)(void) = ^{
  5. a++;
  6. printf("LG - %d",a);
  7. };
  8. block();
  9. return 0;
  10. }

生成block.cpp文件,找到main函数

  1. int main(){
  2. __Block_byref_a_0 a = {
  3. 0,
  4. (__Block_byref_a_0 *)&a,
  5. 0,
  6. sizeof(__Block_byref_a_0),
  7. 18
  8. };
  9. void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
  10. block->FuncPtr(block);
  11. return 0;
  12. }
  • int类型a,对应生成__Block_byref_a_0结构体。成员变量2,对结构体a取地址,转为结构体指针

找到__Block_byref_a_0定义:

  1. struct __Block_byref_a_0 {
  2. void *__isa;
  3. __Block_byref_a_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. int a;
  7. };
  • 其中__forwarding存储的就是a结构体的地址
  • 最后的成员变量a存储18的值
  • 结构体中存储了自身的地址和值

找到__main_block_impl_0的定义:

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. __Block_byref_a_0 *a; // by ref
  5. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
  6. impl.isa = &_NSConcreteStackBlock;
  7. impl.Flags = flags;
  8. impl.FuncPtr = fp;
  9. Desc = desc;
  10. }
  11. };
  12. //参数1
  13. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  14. __Block_byref_a_0 *a = __cself->a; // bound by ref
  15. (a->__forwarding->a)++;
  16. printf("LG - %d",(a->__forwarding->a));
  17. }
  18. //参数2
  19. static struct __main_block_desc_0 {
  20. size_t reserved;
  21. size_t Block_size;
  22. void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  23. void (*dispose)(struct __main_block_impl_0*);
  24. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  25. //参数2使用的copy和dispose函数
  26. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
  27. static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
  • 局部变量a__cself->a指针地址相同,它的值一旦改变,相当于对外界变量的值进行修改

  • 没有__block修饰,属于值拷贝,也就是深拷贝。拷贝的值不可更改,它们指向不同的内存空间

  • 使用__block修饰,属于地址拷贝,也就是浅拷贝。生成的对象指向同一片内存空间,内部修改等同于对外界变量的修改

6. 汇编分析

6.1 流程分析

搭建App项目,写入以下代码:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. __block NSObject *objc = [NSObject alloc];
  4. void (^block)(void) = ^{
  5. NSLog(@"LG_Block %@ ",objc);
  6. };
  7. block();
  8. }

针对block的定义设置断点,运行项目,查看汇编代码
image.png

在项目中,设置objc_retainBlock符号断点
image.png

  • 来自libobjc.A.dylib框架

打开objc4-818.2源码,找到objc_retainBlock函数

  1. id objc_retainBlock(id x) {
  2. return (id)_Block_copy(x);
  3. }
  • 调用_Block_copy函数,但是objc源码中找不到它的实现

在项目中,设置_Block_copy符号断点
image.png
来自libsystem_blocks.dylib框架,但该框架暂未开源,可以在libclosure替代工程中查看

6.2 Block结构

通过cpp分析,Block的本质是结构体。在libclosure-79源码中,来到_Block_copy函数,可以找到Block的真实类型:Block_layout

找到Block_layout的定义:

  1. struct Block_layout {
  2. void * __ptrauth_objc_isa_pointer isa;
  3. volatile int32_t flags; // contains ref count
  4. int32_t reserved;
  5. BlockInvokeFunction invoke;
  6. struct Block_descriptor_1 *descriptor;
  7. // imported variables
  8. };
  • isa:标示Block类型的类

  • flags:标识符,按bit位表示Block的附加信息,类似于isa中的位域

  • reserved:预留位置

  • invoke:函数指针,指向Block实现的调用地址

  • descriptor:附加信息,例如:存储保留变量数、Block的大小、进行copydispose的函数指针

找到flags的标示:

  1. enum {
  2. BLOCK_DEALLOCATING = (0x0001), // runtime
  3. BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
  4. BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
  5. #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
  6. BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler
  7. #endif
  8. BLOCK_IS_NOESCAPE = (1 << 23), // compiler
  9. BLOCK_NEEDS_FREE = (1 << 24), // runtime
  10. BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
  11. BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
  12. BLOCK_IS_GC = (1 << 27), // runtime
  13. BLOCK_IS_GLOBAL = (1 << 28), // compiler
  14. BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
  15. BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
  16. BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
  17. };
  • BLOCK_DEALLOCATING:释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该Block可释放

  • BLOCK_REFCOUNT_MASK:存储引用引用计数的值,是一个可选用参数

  • BLOCK_NEEDS_FREE低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值

  • BLOCK_HAS_COPY_DISPOSE:是否拥有拷贝辅助函数,用于拷贝到堆区,决定block_description_2

  • BLOCK_HAS_CTOR:是否拥有BlockC++析构函数

  • BLOCK_IS_GC:标志是否有垃圾回收,OSX

  • BLOCK_IS_GLOBAL:标志是否是全局Block

  • BLOCK_USE_STRET:与BLOCK_HAS_SIGNATURE相对,判断是否当前Block拥有一个签名,用于runtime时动态调用

  • BLOCK_HAS_SIGNATURE:是否有签名

  • BLOCK_HAS_EXTENDED_LAYOUT:是否有拓展,决定block_description_3

6.3 运行时Copy

进入_Block_copy函数的汇编代码,读取x0寄存器的值,并对其进行打印
image.png

  • 进入_Block_copy函数,当前Block标记为StackBlock

直接运行到函数结尾,读取返回值x0寄存器,并对其进行打印
image.png

  • 程序运行时, 当前Block符合MallocBlock的条件,经过_Block_copy函数,会将Block复制到堆区

6.4 Block调用

_Block_copy函数执行完毕,回到viewDidLoad方法,继续进行Block的调用
image.png

  • ldr x8, [x0, #0x10]x0为当前Block,读取x0 + 16字节的值,赋值给x8
    • Block_layout结构体中,首地址偏移16字节,相当于跳过isaflagsreserved,读取到invoke函数地址,赋值给x8
  • blr x8:跳转到invoke函数地址,相当于Block的调用

6.5 descriptor

_Block_copy函数执行完毕,打印当前BlockMallocBlock
image.png

  • 同时还打印出signaturecopydispose等数据

6.5.1 descriptor类型

signaturecopydispose等数据,存储在Block_layout结构体的descriptor

descriptor分为三种类型:

  • Block_descriptor_1

  • Block_descriptor_2

  • Block_descriptor_3

其中Block_descriptor_1一定存在,Block_descriptor_2Block_descriptor_3为可选

找到descriptor的定义:

  1. #define BLOCK_DESCRIPTOR_1 1
  2. struct Block_descriptor_1 {
  3. uintptr_t reserved;
  4. uintptr_t size;
  5. };
  6. #define BLOCK_DESCRIPTOR_2 1
  7. struct Block_descriptor_2 {
  8. // requires BLOCK_HAS_COPY_DISPOSE
  9. BlockCopyFunction copy;
  10. BlockDisposeFunction dispose;
  11. };
  12. #define BLOCK_DESCRIPTOR_3 1
  13. struct Block_descriptor_3 {
  14. // requires BLOCK_HAS_SIGNATURE
  15. const char *signature;
  16. const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
  17. };
  • Block_descriptor_1:存储预留字段和Block大小
  • Block_descriptor_2:存储copydispose的函数指针
  • Block_descriptor_3:存储signature签名和layout

Block_descriptor_1的读取:

  1. static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
  2. {
  3. return aBlock->descriptor;
  4. }
  • 由于Block_descriptor_1一定存在,直接通过Blockdescriptor读取

Block_descriptor_2的读取:

  1. static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
  2. {
  3. if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
  4. uint8_t *desc = (uint8_t *)aBlock->descriptor;
  5. desc += sizeof(struct Block_descriptor_1);
  6. return (struct Block_descriptor_2 *)desc;
  7. }
  • 通过位与运算,判断Block_descriptor_2不存在,返回NULL
  • 否则,读取Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址
  • 强转为Block_descriptor_2的结构体指针返回

Block_descriptor_3的读取:

  1. static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
  2. {
  3. if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
  4. uint8_t *desc = (uint8_t *)aBlock->descriptor;
  5. desc += sizeof(struct Block_descriptor_1);
  6. if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
  7. desc += sizeof(struct Block_descriptor_2);
  8. }
  9. return (struct Block_descriptor_3 *)desc;
  10. }
  • 通过位与运算,判断Block_descriptor_3不存在,返回NULL
  • 否则,读取Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址
  • 通过位与运算,判断Block_descriptor_2是否存在
    • 存在,再偏移Block_descriptor_2大小,即:Block_descriptor_3的首地址
    • 不存在,强转为Block_descriptor_3的结构体指针返回

因为Block_descriptor_2Block_descriptor_3的内存结构相同,所以偏移Block_descriptor_1之后的地址,即可强转为2,可强转为3

6.5.2 lldb验证descriptor

通过flags进行位与运算,可以得知Block_descriptor_2Block_descriptor_3是否存在

使用x/8g命令,输出Block的内存结构

  1. (lldb) x/8g 0x283aaa430
  2. 0x283aaa430: 0x00000001de1c0880 0x00000000c3000002
  3. 0x283aaa440: 0x00000001021422ac 0x0000000102144018
  4. 0x283aaa450: 0x0000000283aaa400 0x0000000000000000
  5. 0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
  • 0xc3000002Blockflags标识符

验证Block_descriptor_2

  1. //BLOCK_HAS_COPY_DISPOSE = (1 << 25)
  2. (lldb) p/x 1 << 25
  3. (int) $7 = 0x02000000
  4. (lldb) p/x (0xc3000002 & 0x02000000)
  5. (unsigned int) $8 = 0x02000000
  • 运算结果不为0,证明Block_descriptor_2存在

验证Block_descriptor_3

  1. //BLOCK_HAS_SIGNATURE = (1 << 30)
  2. (lldb) p/x 1 << 30
  3. (int) $10 = 0x40000000
  4. (lldb) p/x (0xc3000002 & 0x40000000)
  5. (unsigned int) $16 = 0x40000000
  • 运算结果不为0,证明Block_descriptor_3存在

6.6 Block签名

6.6.1 lldb验证签名

使用x/8g命令,输出Block的内存结构

  1. (lldb) x/8g 0x283aaa430
  2. 0x283aaa430: 0x00000001de1c0880 0x00000000c3000002
  3. 0x283aaa440: 0x00000001021422ac 0x0000000102144018
  4. 0x283aaa450: 0x0000000283aaa400 0x0000000000000000
  5. 0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
  • 首地址平移24字节0x0000000102144018就是descriptor的结构体指针

使用x/8g命令,输出descriptor的内存结构

  1. (lldb) x/8g 0x0000000102144018
  2. 0x102144018: 0x0000000000000000 0x0000000000000028
  3. 0x102144028: 0x00000001021422e0 0x00000001021422f0
  4. 0x102144038: 0x00000001021433e3 0x0000000000000010
  5. 0x102144048: 0x00000001de78a280 0x00000000000007c8
  • 0x00000001021422e0copy函数指针
  • 0x00000001021422f0dispose函数指针
  • 0x00000001021433e3signature签名

0x00000001021433e3进行强转输出

  1. (lldb) po (char *)0x00000001021433e3
  2. "v8@?0"

6.6.2 签名含义

对于签名v8@?0的解释:

  • v:返回值类型viod

  • 8:方法所占用的内存8字节

  • @?参数0类型

  • 0参数0的起始位置,从0字节开始

签名的详细信息,可以使用NSMethodSignaturesignatureWithObjCTypes方法输出

  1. (lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]
  2. <NSMethodSignature: 0xbb2001894a750adf>
  3. number of arguments = 1
  4. frame size = 224
  5. is special struct return? NO
  6. return value: -------- -------- -------- --------
  7. type encoding (v) 'v'
  8. flags {}
  9. modifiers {}
  10. frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
  11. memory {offset = 0, size = 0}
  12. argument 0: -------- -------- -------- --------
  13. type encoding (@) '@?'
  14. flags {isObject, isBlock}
  15. modifiers {}
  16. frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
  17. memory {offset = 0, size = 8}
  • number of arguments = 1:表示传入一个参数

  • is special struct return? NO:没有返回值

  • return value:返回值

    • type encoding (v) 'v'void类型的缩写

    • memory {offset = 0, size = 0}:无返回值,故此size0

  • argument 0参数0

  • type encoding (@) '@?':其中@id类型,?未知类型

  • flags {isObject, isBlock}:即是Object类型,也是Block类型

  • memory {offset = 0, size = 8}参数00字节开始,占8字节

7. 源码分析

使用替代工程libclosure进行源码分析:

Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝

7.1 _Block_copy

进入_Block_copy函数

  1. void *_Block_copy(const void *arg) {
  2. struct Block_layout *aBlock;
  3. // 如果 arg 为 NULL,直接返回 NULL
  4. if (!arg) return NULL;
  5. // The following would be better done as a switch statement
  6. // 强转为 Block_layout 类型
  7. aBlock = (struct Block_layout *)arg;
  8. // 如果现在已经在堆上
  9. if (aBlock->flags & BLOCK_NEEDS_FREE) {
  10. // latches on high
  11. // 就只将引用计数加 1
  12. latching_incr_int(&aBlock->flags);
  13. return aBlock;
  14. }
  15. // 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
  16. else if (aBlock->flags & BLOCK_IS_GLOBAL) {
  17. return aBlock;
  18. }
  19. else {// 栈 - 堆 (编译期)
  20. // Its a stack block. Make a copy.
  21. // block 现在在栈上,现在需要将其拷贝到堆上
  22. // 在堆上重新开辟一块和 aBlock 相同大小的内存
  23. size_t size = Block_size(aBlock);
  24. struct Block_layout *result = (struct Block_layout *)malloc(size);
  25. // 开辟失败,返回 NULL
  26. if (!result) return NULL;
  27. // 将 aBlock 内存上的数据全部复制新开辟的 result 上
  28. memmove(result, aBlock, size); // bitcopy first
  29. #if __has_feature(ptrauth_calls)
  30. // Resign the invoke pointer as it uses address authentication.
  31. result->invoke = aBlock->invoke;
  32. #if __has_feature(ptrauth_signed_block_descriptors)
  33. if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
  34. uintptr_t oldDesc = ptrauth_blend_discriminator(
  35. &aBlock->descriptor,
  36. _Block_descriptor_ptrauth_discriminator);
  37. uintptr_t newDesc = ptrauth_blend_discriminator(
  38. &result->descriptor,
  39. _Block_descriptor_ptrauth_discriminator);
  40. result->descriptor =
  41. ptrauth_auth_and_resign(aBlock->descriptor,
  42. ptrauth_key_asda, oldDesc,
  43. ptrauth_key_asda, newDesc);
  44. }
  45. #endif
  46. #endif
  47. // reset refcount
  48. // 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
  49. result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
  50. // 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
  51. result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
  52. // copy 方法中会调用做拷贝成员变量的工作
  53. _Block_call_copy_helper(result, aBlock);
  54. // Set isa last so memory analysis tools see a fully-initialized object.
  55. // isa 指向 _NSConcreteMallocBlock
  56. result->isa = _NSConcreteMallocBlock;
  57. return result;
  58. }
  59. }
  • _Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区

  • 参数arg就是Block_layout对象

  • 如果原来就在堆上,就将引用计数+1

  • 如果Block在全局区,不用加引用计数,也不用拷贝,直接返回Block本身

  • 如果原来在栈上,会拷贝到堆上,引用计数初始化为1,并且会调用_Block_call_copy_helper方法(如果存在的话)

  • 返回值是拷贝后Block的地址

7.2 _Block_object_assign

7.2.1 cpp分析

打开cpp文件,找到声明Block时的第二个参数,__main_block_desc_0_DATA结构体的定义:

  1. //void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
  2. __main_block_desc_0_DATA = {
  3. 0,
  4. sizeof(struct __main_block_impl_0),
  5. __main_block_copy_0,
  6. __main_block_dispose_0
  7. };
  • __main_block_copy_0Block_descriptor_2中的copy
  • __main_block_dispose_0Block_descriptor_2中的dispose

找到__main_block_copy_0的定义:

  1. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
  2. _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  3. }
  • 调用_Block_object_assign函数

7.2.2 lldb分析

使用x/8g命令,输出Block的内存结构

  1. (lldb) x/8g 0x000000028167eca0
  2. 0x28167eca0: 0x00000001de1c0880 0x00000000c3000002
  3. 0x28167ecb0: 0x0000000104f1a2ac 0x0000000104f1c018
  4. 0x28167ecc0: 0x000000028167ec70 0x0000000000000000
  5. 0x28167ecd0: 0x000021a1de1c6221 0x0000000000000000

使用x/8g命令,输出descriptor的内存结构

  1. (lldb) x/8g 0x0000000104f1c018
  2. 0x104f1c018: 0x0000000000000000 0x0000000000000028
  3. 0x104f1c028: 0x0000000104f1a2e0 0x0000000104f1a2f0
  4. 0x104f1c038: 0x0000000104f1b3e3 0x0000000000000010
  5. 0x104f1c048: 0x00000001de78a280 0x00000000000007c8
  • 0x0000000104f1a2e0copy函数指针

使用dis -s读取汇编代码

  1. (lldb) dis -s 0x0000000104f1a2e0
  2. 004-Block结构与签名`__copy_helper_block_e8_32r:
  3. 0x104f1a2e0 <+0>: add x0, x0, #0x20 ; =0x20
  4. 0x104f1a2e4 <+4>: ldr x1, [x1, #0x20]
  5. 0x104f1a2e8 <+8>: mov w2, #0x8
  6. 0x104f1a2ec <+12>: b 0x104f1a484 ; symbol stub for: _Block_object_assign
  • 调用_Block_object_assign函数

7.2.3 调用时机

_Block_copy函数中,调用_Block_call_copy_helper函数

  1. static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
  2. {
  3. if (auto *pFn = _Block_get_copy_function(aBlock))
  4. pFn(result, aBlock);
  5. }

进入_Block_get_copy_function函数

  1. static inline __typeof__(void (*)(void *, const void *))
  2. _Block_get_copy_function(struct Block_layout *aBlock)
  3. {
  4. if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))
  5. return NULL;
  6. void *desc = _Block_get_descriptor(aBlock);
  7. #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
  8. if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
  9. struct Block_descriptor_small *bds =
  10. (struct Block_descriptor_small *)desc;
  11. return _Block_get_relative_function_pointer(
  12. bds->copy, void (*)(void *, const void *));
  13. }
  14. #endif
  15. struct Block_descriptor_2 *bd2 =
  16. (struct Block_descriptor_2 *)((unsigned char *)desc +
  17. sizeof(struct Block_descriptor_1));
  18. return _Block_get_copy_fn(bd2);
  19. }
  • 如果存在copydispose,通过内存获取Block_descriptor_2的结构体指针
  • 调用_Block_get_copy_fn函数,传入bd2

进入_Block_get_copy_fn函数

  1. static inline __typeof__(void (*)(void *, const void *))
  2. _Block_get_copy_fn(struct Block_descriptor_2 *desc)
  3. {
  4. return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy);
  5. }
  • 经过处理返回Block_descriptor_2下的copy函数

最终,回到_Block_call_copy_helper函数,将copy函数地址赋值pFn,然后通过pFn(result, aBlock)对其进行调用

7.2.4 源码分析

在源码中,找到Block捕获外界变量的种类

  1. enum {
  2. // see function implementation for a more complete description of these fields and combinations
  3. BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
  4. BLOCK_FIELD_IS_BLOCK = 7, // a block variable
  5. BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
  6. BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
  7. BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
  8. };
  • BLOCK_FIELD_IS_OBJECT:普通对象类型

  • BLOCK_FIELD_IS_BLOCKBlock类型作为变量

  • BLOCK_FIELD_IS_BYREF:使用__block修饰的变量

  • BLOCK_FIELD_IS_WEAKweak弱引用变量

  • BLOCK_BYREF_CALLER:返回的调用对象,处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用

进入_Block_object_assign函数

  1. // 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment
  2. // 参数 destAddr 其实是一个二级指针,指向真正的目标指针
  3. void _Block_object_assign(void *destArg, const void *object, const int flags) {
  4. const void **dest = (const void **)destArg;
  5. switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
  6. case BLOCK_FIELD_IS_OBJECT:
  7. /*******
  8. id object = ...;
  9. [^{ object; } copy];
  10. ********/
  11. // _Block_retain_object_default = fn (arc)
  12. // 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数
  13. // 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的
  14. // 可以理解为交给系统 ARC 处理
  15. _Block_retain_object(object);
  16. // 使 dest 指向的目标指针指向 object
  17. *dest = object;
  18. break;
  19. case BLOCK_FIELD_IS_BLOCK:
  20. /*******
  21. void (^object)(void) = ...;
  22. [^{ object; } copy];
  23. ********/
  24. // 使 dest 指向拷贝到堆上object
  25. *dest = _Block_copy(object);
  26. break;
  27. case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
  28. case BLOCK_FIELD_IS_BYREF:
  29. /*******
  30. // copy the onstack __block container to the heap
  31. // Note this __weak is old GC-weak/MRC-unretained.
  32. // ARC-style __weak is handled by the copy helper directly.
  33. __block ... x;
  34. __weak __block ... x;
  35. [^{ x; } copy];
  36. ********/
  37. // 使 dest 指向拷贝到堆上的byref
  38. *dest = _Block_byref_copy(object);
  39. break;
  40. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
  41. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
  42. /*******
  43. // copy the actual field held in the __block container
  44. // Note this is MRC unretained __block only.
  45. // ARC retained __block is handled by the copy helper directly.
  46. __block id object;
  47. __block void (^object)(void);
  48. [^{ object; } copy];
  49. ********/
  50. // 使 dest 指向的目标指针指向 object
  51. *dest = object;
  52. break;
  53. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
  54. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
  55. /*******
  56. // copy the actual field held in the __block container
  57. // Note this __weak is old GC-weak/MRC-unretained.
  58. // ARC-style __weak is handled by the copy helper directly.
  59. __weak __block id object;
  60. __weak __block void (^object)(void);
  61. [^{ object; } copy];
  62. ********/
  63. // 使 dest 指向的目标指针指向 object
  64. *dest = object;
  65. break;
  66. default:
  67. break;
  68. }
  69. }
  • 普通对象类型,交给系统ARC处理,使dest指向的目标指针指向object

  • Block类型作为变量,调用_Block_copy函数,使dest指向拷贝到堆上object

  • 使用__block修饰的变量,调用_Block_byref_copy函数,使dest指向拷贝到堆上的byref

7.3 _Block_byref_copy

进入_Block_byref_copy函数

  1. // 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3
  2. // 被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable
  3. // 原来 byref 的 forwarding 也会指向堆上的 byref;
  4. // 2. 如果 byref 已经在堆上,就只增加一个引用计数。
  5. static struct Block_byref *_Block_byref_copy(const void *arg) {
  6. // arg 强转为 Block_byref * 类型
  7. struct Block_byref *src = (struct Block_byref *)arg;
  8. // 引用计数等于 0
  9. if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
  10. // src points to stack
  11. // 为新的 byref 在堆中分配内存
  12. struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
  13. copy->isa = NULL;
  14. // byref value 4 is logical refcount of 2: one for caller, one for stack
  15. // 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。
  16. // 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack
  17. // one for caller 很好理解,那 one for stack 是为什么呢?
  18. // 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 copy
  19. copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
  20. // 堆上 byref 的 forwarding 指向自己
  21. copy->forwarding = copy; // patch heap copy to point to itself
  22. // 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref
  23. src->forwarding = copy; // patch stack to point to heap copy
  24. // 拷贝 size
  25. copy->size = src->size;
  26. // 如果 src 有 copy/dispose helper
  27. if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
  28. // Trust copy helper to copy everything of interest
  29. // If more than one field shows up in a byref block this is wrong XXX
  30. // 取得 src 和 copy 的 Block_byref_2
  31. struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
  32. struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
  33. // copy 的 copy/dispose helper 也与 src 保持一致
  34. // 因为是函数指针,估计也不是在栈上,所以不用担心被销毁
  35. copy2->byref_keep = src2->byref_keep;
  36. copy2->byref_destroy = src2->byref_destroy;
  37. // 如果 src 有扩展布局,也拷贝扩展布局
  38. if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
  39. struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
  40. struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
  41. // 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上
  42. copy3->layout = src3->layout;
  43. }
  44. // 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数
  45. // 发起第三层拷贝
  46. (*src2->byref_keep)(copy, src);
  47. }
  48. else {
  49. // Bitwise copy.
  50. // This copy includes Block_byref_3, if any.
  51. // 如果 src 没有 copy/dispose helper
  52. // 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3
  53. memmove(copy+1, src+1, src->size - sizeof(*src));
  54. }
  55. }
  56. // already copied to heap
  57. // src 已经在堆上,就只将引用计数加 1
  58. else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
  59. latching_incr_int(&src->forwarding->flags);
  60. }
  61. return src->forwarding;
  62. }
  • Block_byref进行拷贝,属于三层拷贝中的第二层

  • 如果引用计数为0,为新的byref在堆中分配内存

    • 将堆上byrefforwarding指向自己

    • 将原来栈上的byrefforwarding也指向堆上的byref

    • byref_keep发起Block的第三层拷贝

  • 如果已经在堆上,就只将引用计数+1

7.3.1 byref_keep分析

找到byref_keep的定义:

  1. struct Block_byref {
  2. void *isa;
  3. struct Block_byref *forwarding;
  4. volatile int32_t flags; // contains ref count
  5. uint32_t size;
  6. };
  7. struct Block_byref_2 {
  8. // requires BLOCK_BYREF_HAS_COPY_DISPOSE
  9. BlockByrefKeepFunction byref_keep;
  10. BlockByrefDestroyFunction byref_destroy;
  11. };
  12. struct Block_byref_3 {
  13. // requires BLOCK_BYREF_LAYOUT_EXTENDED
  14. const char *layout;
  15. };

对应cpp文件查看:

  1. struct __Block_byref_objc_0 {
  2. void *__isa;
  3. __Block_byref_objc_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. void (*__Block_byref_id_object_copy)(void*, void*);
  7. void (*__Block_byref_id_object_dispose)(void*);
  8. NSObject *objc;
  9. };
  • 源码中的byref_keep,对应cpp中的__Block_byref_id_object_copy

找到__Block_byref_id_object_copy的赋值

  1. __Block_byref_objc_0 objc = {
  2. (void*)0,
  3. (__Block_byref_objc_0 *)&objc,
  4. 33554432,
  5. sizeof(__Block_byref_objc_0),
  6. __Block_byref_id_object_copy_131,
  7. __Block_byref_id_object_dispose_131,
  8. ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"))
  9. };
  • 传入__Block_byref_id_object_copy_131

找到__Block_byref_id_object_copy_131的定义:

  1. static void __Block_byref_id_object_copy_131(void *dst, void *src) {
  2. _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
  3. }

对应源码查看传入的参数:

  1. (*src2->byref_keep)(copy, src);
  • 传入堆上的Block_byref,对应cpp中的__Block_byref_objc_0结构体

7.3.2 byref_keep的结论

调用byref_keep,底层又调用一次_Block_object_assign函数

(char*)dst + 40参数,结构体内存平移,传入的就是objc实例对象

进入_Block_object_assign函数,命中普通对象类型的copy逻辑

将对象拷贝到堆区,完成Block的第三层拷贝

7.4 Block三层拷贝的结论

Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝

  • 【第一层】_Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区

    • 通过_Block_copy_Block_call_copy_helper,调用_Block_object_assign函数

    • 传入Block_byref结构体指针,类型为BLOCK_FIELD_IS_BYREF,调用_Block_byref_copy函数

  • 【第二层】_Block_byref_copy函数,将Block_byref拷贝到堆区

    • 通过byref_keep函数,调用_Block_object_assign函数,传入objc实例对象
  • 【第三层】_Block_object_assign函数,将捕获的外界变量拷贝到堆区

7.5 Block释放

如果Block在堆上,需要进行release。在全局区和栈区的Block,都不需要release

7.5.1 _Block_release

进入_Block_release函数

  1. // block 在堆上,才需要 release,在全局区和栈区都不需要 release.
  2. // 先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁
  3. void _Block_release(const void *arg) {
  4. struct Block_layout *aBlock = (struct Block_layout *)arg;
  5. // 如果 block == nil
  6. if (!aBlock) return;
  7. // 如果 block 在全局区
  8. if (aBlock->flags & BLOCK_IS_GLOBAL) return;
  9. // block 不在堆上
  10. if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
  11. // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 block 需要被销毁
  12. if (latching_decr_int_should_deallocate(&aBlock->flags)) {
  13. // 调用 block 的 dispose helper,dispose helper 方法中会做诸如销毁 byref 等操作
  14. _Block_call_dispose_helper(aBlock);
  15. // _Block_destructInstance 啥也不干,函数体是空的
  16. _Block_destructInstance(aBlock);
  17. free(aBlock);
  18. }
  19. }
  • _Block_copy相似,通过_Block_call_dispose_helper函数,调用_Block_object_dispose函数

7.5.2 _Block_object_dispose

进入_Block_object_dispose函数

  1. // 当 block 和 byref 要 dispose 对象时,它们的 dispose helper 会调用这个函数
  2. void _Block_object_dispose(const void *object, const int flags) {
  3. switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
  4. // 如果是 byref
  5. case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
  6. case BLOCK_FIELD_IS_BYREF:
  7. // get rid of the __block data structure held in a Block
  8. // 对 byref 对象做 release 操作
  9. _Block_byref_release(object);
  10. break;
  11. case BLOCK_FIELD_IS_BLOCK:
  12. // 对 block 做 release 操作
  13. _Block_release(object);
  14. break;
  15. case BLOCK_FIELD_IS_OBJECT:
  16. // 默认啥也不干,但在 _Block_use_RR() 中可能会被 Objc runtime 或者 CoreFoundation 设置一个 release 函数,里面可能会涉及到 runtime 的引用计数
  17. _Block_release_object(object);
  18. break;
  19. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
  20. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
  21. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
  22. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
  23. break;
  24. default:
  25. break;
  26. }
  27. }
  • 普通对象类型,交给系统ARC处理

  • Block类型作为变量,调用_Block_release函数

  • 使用__block修饰的变量,调用_Block_byref_release函数,对byref对象做release操作

7.5.3 _Block_byref_release

进入_Block_byref_release函数

  1. // 对 byref 对象做 release 操作,
  2. // 堆上的 byref 需要 release,栈上的不需要 release,
  3. // release 就是引用计数减 1,如果引用计数减到了 0,就将 byref 对象销毁
  4. static void _Block_byref_release(const void *arg) {
  5. struct Block_byref *byref = (struct Block_byref *)arg;
  6. // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
  7. // 取得真正指向的 byref,如果 byref 已经被堆拷贝,则取得是堆上的 byref,否则是栈上的,栈上的不需要 release,也没有引用计数
  8. byref = byref->forwarding;
  9. // byref 被拷贝到堆上,需要 release
  10. if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
  11. // 取得引用计数
  12. int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
  13. os_assert(refcount);
  14. // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 byref 需要被销毁
  15. if (latching_decr_int_should_deallocate(&byref->flags)) {
  16. if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
  17. struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
  18. // dispose helper 藏在 Block_byref_2 里
  19. (*byref2->byref_destroy)(byref);
  20. }
  21. free(byref);
  22. }
  23. }
  24. }
  • _Block_byref_copy相似,由byref_destroy发起对象的release

对应cpp代码:

  1. static void __Block_byref_id_object_dispose_131(void *src) {
  2. _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
  3. }
  • 调用_Block_object_dispose函数,传入objc实例对象

  • 进入_Block_object_dispose函数,命中普通对象类型的release逻辑

8. 总结

Block类型:

  • GlobalBlock

    • 位于全局区

    • Block内部不能捕获外部变量,或只使用静态变量或全局变量

  • MallocBlock

    • 位于堆区

    • Block内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量

  • StackBlock

    • 位于栈区

    • MallocBlock一样,可以在内部使用局部变量或OC属性。但不能赋值给强引用或Copy修饰的变量

以下四种操作,系统会将Block复制到堆上:

  • 手动Copy

  • Block作为返回值

  • 被强引用或Copy修饰

  • 系统API包含usingBlock

避免Block循环引用的方式:

  • weak-strong-dance

  • Block中强行切断持有者

  • 将持有者作为Block参数进行传递和使用

Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝:

  • 【第一层】_Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区

    • 通过_Block_copy_Block_call_copy_helper,调用_Block_object_assign函数

    • 传入Block_byref结构体指针,类型为BLOCK_FIELD_IS_BYREF,调用_Block_byref_copy函数

  • 【第二层】_Block_byref_copy函数,将Block_byref拷贝到堆区

    • 通过byref_keep函数,调用_Block_object_assign函数,传入objc实例对象
  • 【第三层】_Block_object_assign函数,将捕获的外界变量拷贝到堆区

Block释放:

  • 如果Block在堆上,需要进行release。在全局区和栈区的Block,都不需要release

  • 释放流程和Block拷贝流程基本一致