引言-“毒鸡汤”
一个好的iOS开发,block必不可少,都会用,但是block的底层原理,我们确都是“浅尝辄止”,满足开发就好。人们总说“俗话说”,但是很多俗话说,不都是前人一步一个坑结结实实的踩了,才总结出来的一句话。那么俗话说“逆水行舟,不进则退”,是否也可以用在开发上?我觉得可以。薪水可以变,但是时间不等人。技术深度(广度)应该是至少跑赢时间(可能大盘看多了,收益跑赢大盘这句话就常挂嘴边)。
希望每一位开发者,都应该具备“危机意识”,在有限的时间,学到更多的技术。
话不多说,今天这个文章,是探索block的底层原理。
##block 底层结构
####【1】普通block
按照我们探索类的内存时,用到的探索方式,首先查看编译成底层c++的代码是怎么样的。 在main.m中设计代码如下:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. int a = 1001;
  4. NSString *str = @"百事可乐";
  5. void (^qlBlock)(void) = ^{
  6. NSLog(@"a = %d -- str = %@",a,str);
  7. };
  8. qlBlock();
  9. }
  10. return 0;
  11. }

打开终端,通过xcrun指令将main.m转成main.cpp文件:

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

打开main.cpp,拉到最底下,找到main函数。将括号内的类型转换删除,得到精简代码如下:

  1. int main(int argc, const char * argv[]) {
  2. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  3. int a = 1001;
  4. NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_wv_3h_d7hqj7zz300r8bmzy6_y00000gn_T_main_2abb7f_mi_0;
  5. // __main_block_impl_0函数
  6. void (*qlBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a, str, 570425344);
  7. qlBlock->FuncPtr(qlBlock);
  8. }
  9. return 0;
  10. }

1、void (*qlBlock)(void)赋值为一个函数调用__main_block_impl_0 ()
2、搜索__main_block_impl_0找到block的底层实际上也是一个结构体,并且__main_block_impl_0 ()block的构造函数,源码如下:

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. int a; // 将block外部的变量a捕获成为自己的成员变量
  5. NSString *str; // 将block外部的变量str捕获成为自己的成员变量
  6. // 构造函数:
  7. // 参数1、void *fp:传的是函数__main_block_func_0(),该函数就是block的执行代码块 ^{ },这个fp传给了 FuncPtr,在qlBlock->FuncPtr(qlBlock);中调用
  8. // 参数2、struct __main_block_desc_0 *desc:block的信息结构体
  9. // 参数3、4 外部捕获的变量,a(_a), str(_str) 分别表示_a赋值给a,_str赋值给str
  10. //
  11. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSString *_str, int flags=0) : a(_a), str(_str) {
  12. impl.isa = &_NSConcreteStackBlock;
  13. impl.Flags = flags;
  14. impl.FuncPtr = fp;
  15. Desc = desc;
  16. }
  17. };

3、qlBlock->FuncPtr(qlBlock);调起block,FuncPtr已经在前面的构造函数中赋值为__main_block_func_0 ()函数。因此,此处调的就是这个函数,并把block自己传进去。该block已捕获了外部变量a,str

【2】外部变量加上 __block关键字
我们将外部变量a和str分别加上__block关键字,并在block执行块中修改a和str,main.m代码设计如下:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. __block int a = 1001;
  4. __block NSString *str = @"百事可乐";
  5. void (^qlBlock)(void) = ^{
  6. a = 2002;
  7. str = @"可口可乐";
  8. NSLog(@"a = %d -- str = %@",a,str);
  9. };
  10. qlBlock();
  11. }
  12. return 0;
  13. }

打开终端,通过xcrun指令将main.m转成main.cpp文件:

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

打开main.cpp,拉到最底下,找到main函数。将括号内的类型转换删除,得到精简代码如下:

  1. 1block的结构体
  2. struct __main_block_impl_0 {
  3. struct __block_impl impl;
  4. struct __main_block_desc_0* Desc;
  5. __Block_byref_a_0 *a; // by ref
  6. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { // 将 _a-> __forwarding赋值给a
  7. impl.isa = &_NSConcreteStackBlock;
  8. impl.Flags = flags;
  9. impl.FuncPtr = fp;
  10. Desc = desc;
  11. }
  12. };
  13. 2main函数
  14. int main(int argc, const char * argv[]) {
  15. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  16. __Block_byref_a_0 a = {0,&a, 0, sizeof(__Block_byref_a_0), 1001};
  17. // __main_block_impl_0()
  18. void (*qlBlock)(void) = (__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344));
  19. // 调用
  20. qlBlock->FuncPtr)(qlBlock);
  21. }
  22. return 0;
  23. }
  24. 3、生成的a结构体
  25. struct __Block_byref_a_0 {
  26. void *__isa;
  27. __Block_byref_a_0 *__forwarding;
  28. int __flags;
  29. int __size;
  30. int a;
  31. };
  32. 4block执行的代码块
  33. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {// 传入block自己
  34. // 获取block生成的并赋值好的a
  35. __Block_byref_a_0 *a = __cself->a; // bound by ref
  36. // 结构体a通过__forwarding获取结构体a内部的成员变量a,并进行修改
  37. (a->__forwarding->a) = 2002;
  38. NSLog((NSString *)&__NSConstantStringImpl__var_folders_wv_3h_d7hqj7zz300r8bmzy6_y00000gn_T_main_6d01e1_mi_0,(a->__forwarding->a));
  39. }

由编译后的源码可知,
1、外部变量加了__block后,block对外部变量的捕获,不再是单纯的生成与之对应的成员变量,而是在内部生成__Block_byref_a_0 *a的结构体指针变量。
2、在main函数中,先将外部的__block int a编译后,生成一个结构体__Block_byref_a_0(其内部如上3、生成的a结构体,该结构体内部生成一个对应的int a,用于接收外部int a的初始值1001,同时将外部int a的地址&a赋值给__forwarding),初始化赋值代码:__Block_byref_a_0 a = {0,&a, 0, sizeof(__Block_byref_a_0), 1001};
3、到此为止,生成的结构体a的__forwarding为外部int a的地址,
block结构体内部的***a**与外部变量**a**所指向的内存空间是同一个,捕获的外部变量会随着block生成的对应成员变量改变而改变。
####block 底层结构小结
1、block的本质是一个结构体;
2、block的定义,是通过block的结构体内部的构造函数,对block内部的impl、desc进行赋值。
3、结构体impl内部有4个成员变量:isa、Flags、Reserved、FuncPtr,在block构造函数赋值时,将isa赋值为默认的NSConcreteStackBlock地址(此为编译阶段,运行时这个isa会赋值成真实类型),FuncPtr 的赋值是定义的block执行代码块。
4、block捕获外部变量,是在block内部生成对应的成员变量,并通过构造函数将捕获的变量赋值给内部生成的成员变量—(值拷贝)
5、block捕获的外部变量,如果用__block修饰,则不再是将值传给block内部生成的变量,而是将外部变量的地址传给内部成员变量,达到你动我动的效果(指针拷贝)

block 源码

1、在苹果官网Source Browser下载libclosure-79,解压打开Blocks工程。
2、在原来的objc工程main.m中,定一个最简单的block,并打上断点,运行到断点后,打开汇编(Debug--Debug Workflow -- Always Show Disassembly)。015-iOS底层原理-block - 图10
3、在汇编中,查看block的类型,以及将要call的符号015-iOS底层原理-block - 图11
4、点击step into,进入到objc_retainBlock函数中,可看到将要访问_Block_copy此函数即可将 stack block 复制成 malloc block015-iOS底层原理-block - 图12
5、继续点击step into会回到main的汇编。因此_Block_copy是我们继续探索的函数符号。打开1的Blocks工程。全局搜索_Block_copy,源码如下(注意看注释)

  1. // Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
  2. void *_Block_copy(const void *arg) {
  3. // block的真实结构Block_layout
  4. struct Block_layout *aBlock;
  5. if (!arg) return NULL;
  6. // The following would be better done as a switch statement
  7. aBlock = (struct Block_layout *)arg;
  8. // 如果是释放状态,没必要进行下一步处理,直接返回aBlock
  9. if (aBlock->flags & BLOCK_NEEDS_FREE) {
  10. // latches on high
  11. latching_incr_int(&aBlock->flags);
  12. return aBlock;
  13. }
  14. else if (aBlock->flags & BLOCK_IS_GLOBAL) {
  15. // 如果是global类型的block,直接返回。
  16. return aBlock;
  17. }
  18. else {
  19. // 如果不是global block,那么只有两种情况:1、栈block(StackBlock),2、堆block(MallocBlock)
  20. // 在编译阶段,暂时会将block标记成栈block,是因为在编译阶段,若是对block进行malloc开辟内存的话,会增加编译器的压力
  21. // 来到运行时(runtime),block捕获了外部变量,则需要将栈block 的大小,malloc(size)开辟一个内存空间
  22. // Its a stack block. Make a copy.
  23. size_t size = Block_size(aBlock);
  24. struct Block_layout *result = (struct Block_layout *)malloc(size);
  25. if (!result) return NULL;
  26. // 开始copy
  27. memmove(result, aBlock, size); // bitcopy first
  28. #if __has_feature(ptrauth_calls)
  29. // Resign the invoke pointer as it uses address authentication.
  30. // invoke的copy
  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. // 重置refcount和配置flags
  49. result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
  50. result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
  51. _Block_call_copy_helper(result, aBlock);
  52. // Set isa last so memory analysis tools see a fully-initialized object.
  53. // 将block类型标记为堆block(MallocBlock)
  54. result->isa = _NSConcreteMallocBlock;
  55. return result;
  56. }
  57. }

6、通过源码可知流程如下:
a)oc定义的block 不 捕获外部变量 ——>编译器—->global block——>运行时—->return global block
b)oc定义的block捕获外部变量 ——>编译器—->stack block ——>运行时_Block_copy—->malloc block
7、block的底层结构为struct Block_layout结构体,源码如下(注意注释):

  1. struct Block_layout {
  2. // isa的指向:也可以说是block的类型:1、global block;2、stack block;3、malloc block
  3. void * __ptrauth_objc_isa_pointer isa;
  4. // 标识
  5. volatile int32_t flags; // contains ref count
  6. int32_t reserved;
  7. // 执行函数
  8. BlockInvokeFunction invoke;
  9. // block的信息描述
  10. struct Block_descriptor_1 *descriptor;
  11. // imported variables
  12. };
  • 7.1、结构体Block_layout的成员变量struct Block_description_1开始,往下都是为可选参数,也就是说,block的描述信息,可以在以下结构体中选择(但是具体如何设置,根据#define条件来配置,在哪儿配置,我们不得而知,然我们可以通过反推法来了解,即不知道setter,通过getter来了解):
  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. };
  18. struct Block_descriptor_small {
  19. uint32_t size;
  20. int32_t signature;
  21. int32_t layout;
  22. /* copy & dispose are optional, only access them if
  23. Block_layout->flags & BLOCK_HAS_COPY_DIPOSE */
  24. int32_t copy;
  25. int32_t dispose;
  26. };
  • 7.2 block desc的几种情况
  • 7.2.1 Block_descriptor_1:搜索_Block_get_descriptor,源码如下:
  1. // getter 函数
  2. static inline void *_Block_get_descriptor(struct Block_layout *aBlock)
  3. {
  4. void *descriptor;
  5. #if __has_feature(ptrauth_signed_block_descriptors)
  6. if (!(aBlock->flags & BLOCK_SMALL_DESCRIPTOR)) {
  7. descriptor =
  8. (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
  9. } else {
  10. uintptr_t disc = ptrauth_blend_discriminator(
  11. &aBlock->descriptor, _Block_descriptor_ptrauth_discriminator);
  12. descriptor = (void *)ptrauth_auth_data(
  13. aBlock->descriptor, ptrauth_key_asda, disc);
  14. }
  15. #elif __has_feature(ptrauth_calls)
  16. descriptor = (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
  17. #else
  18. descriptor = (void *)aBlock->descriptor;
  19. #endif
  20. return descriptor;
  21. }
  22. // setter函数
  23. static inline void _Block_set_descriptor(struct Block_layout *aBlock, void *desc)
  24. {
  25. aBlock->descriptor = (struct Block_descriptor_1 *)desc;
  26. }
  • 7.2.2 Block_descriptor_2:全局搜索Block_descriptor_2,源码如下(看下图解释):
  1. static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
  2. {
  3. uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
  4. desc += sizeof(struct Block_descriptor_1);// 看下图解释
  5. return (struct Block_descriptor_2 *)desc;
  6. }

015-iOS底层原理-block - 图13

  • 7.2.3 Block_descriptor_3:全局搜索Block_descriptor_3,源码如下(看上图解释):
    **Block_descriptor_2存在**,则指针偏移在desc的基础上,加上Block_descriptor_1的大小,再加上Block_descriptor_2的大小,即可得到Block_descriptor_3。(desc3 = desc + sizeOf(desc1) + sizeOf(desc2))
    **Block_descriptor_2不存在**,则指针偏移在desc的基础上,加上Block_descriptor_1的大小,即可得到Block_descriptor_3。(desc3 = desc + sizeOf(desc1) )
  1. static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
  2. {
  3. uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
  4. desc += sizeof(struct Block_descriptor_1);
  5. if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
  6. desc += sizeof(struct Block_descriptor_2);
  7. }
  8. return (struct Block_descriptor_3 *)desc;
  9. }

block 调试
1、未捕获外部变量:回到objc工程,在main.m添加如下代码,并添加断点:
015-iOS底层原理-block - 图14
2、捕获外部变量:015-iOS底层原理-block - 图15
3、添加_Block_copy符号断点,运行时,先暂时关闭_Block_copy断点,因为在运行时会有一些系统的block会调用此函数进行复制,我们只需要在自定义的block断点停住时,把_Block_copy断点激活即可。打开Debug 汇编
015-iOS底层原理-block - 图16
3.1:我们通过lldb调试的指令register read读取寄存器的情况(真机的寄存器名字为x0,x1等),最终看到在_Block_copy函数内部的汇编block的类型的变化,对应上面的_Block_copy源码即可。015-iOS底层原理-block - 图17
3.2:继续点击step over,一直回到main.m时,在block调用处打上断点。通过lldb调试的指令register read读取寄存器的情况,如图所示:015-iOS底层原理-block - 图18
若外部变量为对象,则会多一个copy和dispose。即7.1中的block信息描述选择。

  1. <__NSStackBlock__: 0x7ffeefbff428>
  2. signature: "v8@?0"
  3. invoke : 0x1000038e0 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__main_block_invoke)
  4. copy : 0x100003910 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__copy_helper_block_e8_32s)
  5. dispose : 0x100003950 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__destroy_helper_block_e8_32s)

3.3:signature的值可参考前面的文章类的方法底层,是一样的。具体例子:lldb调试:[NSMethodSignature signatureWithObjCTypes:"v8@?0"];打印结果如下(注意注释):

  1. (lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"];
  2. <NSMethodSignature: 0x7afa2cf5db0b894b>
  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 (@) '@?' // 证明了block的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}

block 捕获
####【1】无__block修饰

  • 1.1 在main.m中设计代码如下:
  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. NSObject *obj = [NSObject alloc];
  4. void (^ qlBlock)(void) = ^{
  5. NSLog(@"---%@---",obj);
  6. };
  7. qlBlock();
  8. }
  9. return 0;
  10. }
  • 1.2 通过xcrun将源码编译成c/c++。
  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

将代码进行排版,清除一些类型强转,简化后的代码如下:

015-iOS底层原理-block - 图19

  • 1.3 从入口函数main()中可以看到,^{};最终翻译成函数__main_block_impl_0(),该函数的入参分析:
  • a)__main_block_func_0是一个函数指针,是block回调执行的代码块
  • b)(重点)&__main_block_desc_0_DATA是取结构体__main_block_desc_0的地址,该结构体赋值情况,即:
  1. __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

具体对应的变量为:

sizet reserved = 0;
size_t Block_size = sizeof(struct **main_block_impl_0);
void (_copy)(struct __main_block_impl_0
, struct main_block_impl_0*) = mainblock_copy_0;
void (_dispose)(struct __main_block_impl_0
) = **main_block_dispose_0;

  • c)obj即我们定义的obj变量,obj作为参数传入block内部生成的obj变量,并将外部obj赋值给内部obj。由于obj是一个NSObject *的指针,此处obj传入相当于block生成的obj与外部的obj指向同一片堆空间。(block捕获外部变量的小结在上面的 block 底层结构小结
  • d)570425344数字参数,是一个暂时用不到的参数。暂不考虑。
    ####【2】有__block修饰

我们在NSObject *obj前加入__block,然后 按照第【1】步的步骤,将main.m编译成c/c++。
将代码进行排版,清除一些类型强转,简化后的代码如下:015-iOS底层原理-block - 图20

  • 2.1 与前面的无 __block 修饰的外部变量捕获相比,加了__block后,外部变量NSObject *obj逻辑编译成了一个结构体__Block_byref_obj_0 obj,该结构体源码如下:015-iOS底层原理-block - 图21
  • 2.2 用__block修饰的外部遍历,block构造函数__main_block_impl_0传入的是&obj,也就是obj本身的地址,也就是说,外部的obj随着block内部的obj的变化而变化。
  • 2.3 无__block修饰的外部变量,block构造函数__main_block_impl_0传入的是obj,也就是obj指针指向的堆地址
    ####【3】_Block_object_assign
  • 【3.1】 通过上面将block编译成c/c++文件的分析。我们可以看到在封装block信息描述的结构体desc中,传入了一__main_block_copy_0函数和一个__main_block_dispose_0函数。两个函数的实现如下:
  1. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
  2. _Block_object_assign(&dst->obj, src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
  3. }
  4. static void __main_block_dispose_0(struct __main_block_impl_0*src) {
  5. _Block_object_dispose(src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
  6. }

_Block_object_assign()传入了三个参数:1、&dst->obj:目标block的obj地址;2、src->obj:原block的obj变量;3、8:BLOCK_FIELD_IS_BYREF,表示该对象用__block 修饰了。

The flags parameter of _Block_object_assign and _Block_object_dispose is set to
BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.

即:

  • 捕获的外部变量为普通oc对象,则传入3;
  • 捕获的外部变量为其他block,则传入7;
  • 捕获的外部变量为用__block修饰的变量,则传入8;
  • 【3.2】 打开Blocks.xcodeproj工程,全局搜索_Block_object_assign,找到源码如下:
  1. void _Block_object_assign(void *destArg, const void *object, const int flags) {
  2. const void **dest = (const void **)destArg;
  3. switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
  4. case BLOCK_FIELD_IS_OBJECT:
  5. /*******
  6. id object = ...;
  7. [^{ object; } copy];
  8. ********/
  9. _Block_retain_object(object);
  10. *dest = object;
  11. break;
  12. case BLOCK_FIELD_IS_BLOCK:
  13. /*******
  14. void (^object)(void) = ...;
  15. [^{ object; } copy];
  16. ********/
  17. *dest = _Block_copy(object);
  18. break;
  19. case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
  20. case BLOCK_FIELD_IS_BYREF:
  21. /*******
  22. // copy the onstack __block container to the heap
  23. // Note this __weak is old GC-weak/MRC-unretained.
  24. // ARC-style __weak is handled by the copy helper directly.
  25. __block ... x;
  26. __weak __block ... x;
  27. [^{ x; } copy];
  28. ********/
  29. *dest = _Block_byref_copy(object);
  30. break;
  31. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
  32. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
  33. /*******
  34. // copy the actual field held in the __block container
  35. // Note this is MRC unretained __block only.
  36. // ARC retained __block is handled by the copy helper directly.
  37. __block id object;
  38. __block void (^object)(void);
  39. [^{ object; } copy];
  40. ********/
  41. *dest = object;
  42. break;
  43. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
  44. case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
  45. /*******
  46. // copy the actual field held in the __block container
  47. // Note this __weak is old GC-weak/MRC-unretained.
  48. // ARC-style __weak is handled by the copy helper directly.
  49. __weak __block id object;
  50. __weak __block void (^object)(void);
  51. [^{ object; } copy];
  52. ********/
  53. *dest = object;
  54. break;
  55. default:
  56. break;
  57. }
  58. }

分析:
通过flags & BLOCK_ALL_COPY_DISPOSE_FLAGS得到捕获的变量是什么类型,通过switch分支进行分类处理。

  • 3.2.1 case BLOCK_FIELD_IS_OBJECT: 如果捕获的变量是oc object ,则将捕获的object进行copy。(通过_Block_retain_object()回调给_Block_retain_object_default(),并将原block赋值给目标block*dest = object;
  • 3.2.2 case BLOCK_FIELD_IS_BLOCK: 如果捕获的对象是一个block,则走_Block_copy()函数,并将结果赋值给目标block。
  • 3.2.3(后面重点分析case BLOCK_FIELD_IS_BYREF : 如果捕获的变量是用__block修饰的或者__weak __block修饰的,将原block通过_Block_byref_copy()赋值给目标block。
  • 3.2.4 其余情况均是将原block直接赋值给目标block*dest = object;

【4】_Block_byref_copy

全局搜索_Block_byref_copy,得到源码如下:

  1. static struct Block_byref *_Block_byref_copy(const void *arg) {
  2. struct Block_byref *src = (struct Block_byref *)arg;
  3. if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
  4. // src points to stack
  5. struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
  6. copy->isa = NULL;
  7. // byref value 4 is logical refcount of 2: one for caller, one for stack
  8. copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
  9. copy->forwarding = copy; // patch heap copy to point to itself
  10. src->forwarding = copy; // patch stack to point to heap copy
  11. copy->size = src->size;
  12. if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
  13. // Trust copy helper to copy everything of interest
  14. // If more than one field shows up in a byref block this is wrong XXX
  15. struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
  16. struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
  17. copy2->byref_keep = src2->byref_keep;
  18. copy2->byref_destroy = src2->byref_destroy;
  19. if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
  20. struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
  21. struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
  22. copy3->layout = src3->layout;
  23. }
  24. (*src2->byref_keep)(copy, src);
  25. }
  26. else {
  27. // Bitwise copy.
  28. // This copy includes Block_byref_3, if any.
  29. memmove(copy+1, src+1, src->size - sizeof(*src));
  30. }
  31. }
  32. // already copied to heap
  33. else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
  34. latching_incr_int(&src->forwarding->flags);
  35. }
  36. return src->forwarding;
  37. }
  • 【4.1】判断语句if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0),其中BLOCK_REFCOUNT_MASK引用计数相关的掩码。如果此处判断 == 0,说明引用计数为0,并没有将block copy到堆上。反之(src->forwarding->flags & BLOCK_REFCOUNT_MASK) != 0,则说明已经将原block copy到堆上。接着判断flags是否需要释放,需要释放则进行flags的重新赋值
    -【4.2】未copy到堆上,流程如图所示:015-iOS底层原理-block - 图22
    -【4.3】判断语句if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE)如果成立,说明已经捕获到了外部变量,具体如图所示:015-iOS底层原理-block - 图23

【5】byref_keep

由上图我们可知byref_keep()是为了保存(保活)外部捕获变量的生命周期,如果不进行keep,则有可能会出现src为空了之后,开辟的空间的内容也被清空了。
byref_keep的源码为:

  1. struct Block_byref_2 {
  2. // requires BLOCK_BYREF_HAS_COPY_DISPOSE
  3. BlockByrefKeepFunction byref_keep;
  4. BlockByrefDestroyFunction byref_destroy;
  5. };

返回到main_byref.cpp文件中,找到struct __Block_byref_obj_0{}结构体,内部的copydispose函数分别赋值给了byref_keepbyref_destroy。也就是说在编译后的cpp文件中,在main函数里,block修饰的NSObject *obj,编译所生成的结构体的赋值,`Block_byref_id_object_copy_131赋值给了结构体Block_byref_obj_0copy函数;Block_byref_id_object_dispose_131赋值给了__Block_byref_obj_0dispose`函数。
也就是说:

byref_keep = Block_byref_id_object_copy_131函数
byref_destroy =
Block_byref_id_object_dispose_131函数

由此可知,byref_keep函数的调用,是调用__Block_byref_obj_0copy函数。也就是把_Block_object_assign调用一遍,即

  1. // (*src2->byref_keep)(copy, src);
  2. static void __Block_byref_id_object_copy_131(void *dst, void *src) {
  3. _Block_object_assign(dst + 40, * (src + 40), 131);
  4. }
  • a)dst:即byref_keep函数的第1个参数copy:新开辟的block(目标block)。
  • b)src:即byref_keep函数的第2个参数src:原来的block。
  • c)dst+40src + 40,是在目标block的首地址平移40字节,对应结构体__Block_byref_obj_0内存结构,平移40字节,得到的是NSObject *obj,计算方式如下:
  1. struct __Block_byref_obj_0 {
  2. void *__isa; // 8
  3. __Block_byref_obj_0 *__forwarding;// 8
  4. int __flags; // 4
  5. int __size; // 4
  6. void (*__Block_byref_id_object_copy)(void*, void*);// 8
  7. void (*__Block_byref_id_object_dispose)(void*);// 8
  8. NSObject *obj;
  9. };

_Block_object_assign(dst + 40, * (src + 40), 131);变成为_Block_object_assign(obj, * obj, 131);。由上面_Block_object_assign函数的源码可知,此刻的switch分支,走的就是BLOCK_FIELD_IS_OBJECT分支。
因此,block捕获__block修饰的外部变量,
##总结

  • block的本质是一个结构体,并由结构体的构造函数初始化和赋值
  • block的类型有3种global blockstack blockmalloc block
  • block 不捕获外部变量则为 global block
  • block捕获外部变量编译时为stack block运行时_Block_copy后变成malloc block
  • 结构体impl内部有4个成员变量:isa、Flags、Reserved、FuncPtr,在block构造函数赋值时,将isa赋值为默认的NSConcreteStackBlock地址(此为编译阶段,运行时这个isa会赋值成真实类型),FuncPtr 的赋值是定义的block执行代码块。
  • block结构体中的Block_descriptor_1Block_byref一样内存是连续的可通过指针平移来获取对应位置的值。
    Block_descriptor_1—-Block_descriptor_2—-Block_descriptor_3内存是连续的,
    Block_byref—-Block_byref_2—-Block_byref_3内存是连续的
  • block捕获外部变量,是在block内部生成对应的成员变量,并通过构造函数将捕获的变量赋值给内部生成的成员变量 —(值拷贝)
  • 捕获用__block修饰的外部变量(指针拷贝)3层copy
    (a)将block从copy到
    (b)接着block捕获Block_byref结构体,并对其进行copy
    (c)Block_byref对内部的变量(本文为NSObject *obj)进行copy。