IMG_5763.JPG

前言

  • iOS-block(上)对block种类进行了划分,由此也可以引出几个问题:
    • 堆Block是从栈Block复制过去的,这个底层是如何实现的呢?
    • block是如何捕获变量的,以及是如何存储的
    • block的底层源码是怎样的
    • block编译成一个什么样的数据结构
    • block invoke 中的isa、签名、捕获变量何去何从(保存、释放)

      block编译结构初探

      示例1

  1. 建立一个.c文件,包含了局部变量、block、block调用
  2. 通过xcrun编译为.cpp文件
    • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp
  1. #include "stdio.h"
  2. int main() {
  3. int a = 18;
  4. void(^block)(void) = ^{
  5. printf("RaindyCool -");
  6. };
  7. block();
  8. return 0;
  9. }
  1. 在.cpp文件中找到main函数,将其强转等代码后进行精简
    • block捕获变量会生成相应的成员变量,而等号右侧是函数指针
    • block()调用相当于函数调用FuncPtr(block)

image.png

  • block打印中加入对a的捕获printf(“RaindyCool - %d”, a),编译后得到.cpp文件
  • impl.FuncPtr = fp 函数式保存,在block()调用时调用(__block_impl *)block
  • 两个a的值相同,地址不同,所以是值拷贝

image.png

  • 进一步改动,对a进行block修饰,同时在block中执行a++,此时编译后生成的是Block_byref_a_0

image.png

  • Block_byref_a_0底层是结构体,其(Block_byref_a_0 *)类型的forwarding指针保存了传入的变量地址,然后通过参数_a->forwarding传递给了cself->a,使得block内部同外部可以访问同一片内存空间
  • block主要作用,生成了一个block_byref_a_0的结构体,然后传给block指针地址

image.png

总结

  • 当捕获外部变量时,block结构体中会增加一个成员变量a,并且构造函数也会增加一个参数a
  • 如果没有__block修饰,则通过值拷贝方式,对成员变量a进行赋值
  • block执行时,通过cself->a,获取结构体中a的值,赋给Block_bref_a_0 *a

    示例2

  • viewController建立如下代码,通过xcrun或clang编译得到.cpp文件 ```objectivec

  • (void)viewDidLoad { [super viewDidLoad]; NSObject *objc = [NSObject alloc];

    __block NSObject *objc1 = [NSObject alloc]; void (^block1)(void) = ^{

    1. NSLog(@"LG_Block %@ ",objc1);

    }; block1();

}

  1. - **编译的文件中,isa指向了栈block,在运行时,栈block变为堆**
  2. - **fp是函数式保存,赋值给了impl -> FuncPtr,在block()调用时,调用FuncPtr,即调用fp**
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629812053932-16ee26d5-b9f6-49c1-b6f5-3d21fd445ae6.png#clientId=u49fed5a2-1f7c-4&from=paste&height=243&id=u35906799&margin=%5Bobject%20Object%5D&name=image.png&originHeight=486&originWidth=2606&originalType=binary&ratio=1&size=185948&status=done&style=none&taskId=u6ca93c4a-01ff-4b8b-82af-42668528b22&width=1303)
  4. <a name="AbfhO"></a>
  5. #### 总结
  6. - block是一个结构体,也可以认为是一个函数或者参数,通过函数式编程,将block任务保存到block结构体的FuncPtr属性中。block()执行调用时即调用了impl->FuncPtr
  7. ![未命名文件 (7).jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/21860440/1630033221144-efe5c0b3-4293-4872-8475-1f5d6a802509.jpeg#clientId=u6f551bc9-2a68-4&from=ui&id=uc03eebcc&margin=%5Bobject%20Object%5D&name=%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20%287%29.jpg&originHeight=604&originWidth=984&originalType=binary&ratio=1&size=60758&status=done&style=none&taskId=u09ce35a2-f24e-4a25-8996-c21ea805749)
  8. <a name="sja0p"></a>
  9. ## 底层源码
  10. - **建立以下block代码,NSLog处断点调试,发现block底层是调用了objc_retainBlock**
  11. ```objectivec
  12. NSObject *objc = [NSObject alloc];
  13. __block NSObject *objc1 = [NSObject alloc];
  14. void (^block1)(void) = ^{
  15. NSLog(@"CC_Block %@ ",objc1);
  16. };
  17. block1();
  • objc_retainBlock 则是调用了_Block_copy(运行时调用),经过_Block_copy则将block从栈拷贝到了堆,但是在objc底层库中我们并未找到_Block_copy的调用,而_Block_copy是在libsystem_blocks.dylib库中,但该库并未开源。解决方案有两个:

    1. 反汇编查看
    2. 替代工程libclosure-79
      1. id objc_retainBlock(id x) {
      2. return (id)_Block_copy(x);
      3. }
  • libclosure-79源码中,_Block_copy调用如下

    • 首先定义了Block_layout结构体(详解),对arg进行了类型转换
    • 在编译器只有堆Block,运行时期发现是栈Block,同时还捕获了外部变量,则在堆上开辟内存空间,进行copy操作,同时将Block类型标记为堆Block ```objectivec // Copy, or bump refcount, of a block. If really copying, call the copy helper if present. void _Block_copy(const void arg) { struct Block_layout *aBlock;

      if (!arg) return NULL;

      // The following would be better done as a switch statement aBlock = (struct Block_layout )arg; if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } else {// 栈 - 堆 (编译期) // Its a stack block. Make a copy. size_t size = Block_size(aBlock); struct Block_layout result = (struct Block_layout *)malloc(size); if (!result) return NULL; memmove(result, aBlock, size); // bitcopy first

      if __has_feature(ptrauth_calls)

      // Resign the invoke pointer as it uses address authentication. result->invoke = aBlock->invoke;

if __has_feature(ptrauth_signed_block_descriptors)

  1. if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
  2. uintptr_t oldDesc = ptrauth_blend_discriminator(
  3. &aBlock->descriptor,
  4. _Block_descriptor_ptrauth_discriminator);
  5. uintptr_t newDesc = ptrauth_blend_discriminator(
  6. &result->descriptor,
  7. _Block_descriptor_ptrauth_discriminator);
  8. result->descriptor =
  9. ptrauth_auth_and_resign(aBlock->descriptor,
  10. ptrauth_key_asda, oldDesc,
  11. ptrauth_key_asda, newDesc);
  12. }

endif

endif

  1. // reset refcount
  2. result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
  3. result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
  4. _Block_call_copy_helper(result, aBlock);
  5. // Set isa last so memory analysis tools see a fully-initialized object.
  6. result->isa = _NSConcreteMallocBlock;
  7. return result;
  8. }

}

  1. <a name="rQ4Ap"></a>
  2. ### 示例3
  3. - 在viewDidLoad中加入以下代码,断点在block,运行
  4. - 同时下符号断点_Block_copy
  5. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629961337778-64fd34b5-0282-4fc0-b374-4a2805b3adf2.png#clientId=u2eeb6c64-fbca-4&from=paste&height=114&id=ubdfb0c35&margin=%5Bobject%20Object%5D&name=image.png&originHeight=228&originWidth=1312&originalType=binary&ratio=1&size=57787&status=done&style=none&taskId=ub549d4e6-7107-4f72-a1c2-e285a99b9ba&width=656)
  6. - 通过register read 查看x0寄存器,po得到的内存地址
  7. - 此时我们得到了一个栈Block,同时还展示了其包含的结构信息
  8. - signature,block的签名,方法调用本质是查找消息,消息找不到时(比如hook)会进入消息转发流程,首先要获取其签名信息,才能包装成invocation进行相关处理,所以签名是必要信息
  9. - po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]查看内存信息![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629962855286-82b6421b-b574-456e-a289-b1d6a43942a5.png#clientId=u2eeb6c64-fbca-4&from=paste&height=290&id=ub7e825b0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=580&originWidth=1778&originalType=binary&ratio=1&size=145973&status=done&style=none&taskId=u8264caeb-a402-473f-9dcd-6e8eaedad6e&width=889)
  10. - invoke,函数的调用者指针
  11. ```objectivec
  12. (lldb) register read x0
  13. x0 = 0x000000016d85cec8
  14. (lldb) po 0x000000016d85cec8
  15. <__NSStackBlock__: 0x16d85cec8>
  16. signature: "v8@?0"
  17. invoke : 0x1025a4aa0 (/private/var/containers/Bundle/Application/38934E2E-F16E-4B76-AD32-3FBF58632FEC/005---GCD进阶使用(下).app/005---GCD进阶使用(下)`__29-[ViewController viewDidLoad]_block_invoke)
  18. copy : 0x1025a4ae0 (/private/var/containers/Bundle/Application/38934E2E-F16E-4B76-AD32-3FBF58632FEC/005---GCD进阶使用(下).app/005---GCD进阶使用(下)`__copy_helper_block_e8_32r)
  19. dispose : 0x1025a4b18 (/private/var/containers/Bundle/Application/38934E2E-F16E-4B76-AD32-3FBF58632FEC/005---GCD进阶使用(下).app/005---GCD进阶使用(下)`__destroy_helper_block_e8_32r)
  20. (lldb)
  • 继续执行,在_Block_copy结束时断点,查看x0,发现已经变为了堆block

1629962246727.jpg

Block_layout

  • isa -> 指向,区分栈block、堆block
  • flags -> 可以存储很多数据信息,包括引用计数
  • reserved -> 流程数据
  • invoke -> 调用函数
  • descriptor -> 相关描述,比如是否正在析构、是否有keep函数

    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. };
  • Block_layout中除了Block_descriptor_1还有Block_descriptor_2、Block_descriptor_3的定义

    • Block_descriptor_2包含了两个函数,分别是copy函数和dispose函数
    • Block_descriptor_3也包含了两个函数,分别是signature签名和layout ```objectivec

      define BLOCK_DESCRIPTOR_1 1

      struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; };

define BLOCK_DESCRIPTOR_2 1

struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; BlockDisposeFunction dispose; };

define BLOCK_DESCRIPTOR_3 1

struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char signature; // 签名定义在description3中 const char layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT };

//====================== static struct Block_descriptor_2 _Block_descriptor_2(struct Block_layout aBlock) { uint8_t desc = (uint8_t )_Block_get_descriptor(aBlock); desc += sizeof(struct Block_descriptor_1); return (struct Block_descriptor_2 *)desc; }

static struct Block_descriptor_3 _Block_descriptor_3(struct Block_layout aBlock) { uint8_t desc = (uint8_t )_Block_get_descriptor(aBlock); desc += sizeof(struct Block_descriptor_1); if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) { desc += sizeof(struct Block_descriptor_2); } return (struct Block_descriptor_3 *)desc; }

  1. - **Block_descriptor_2Block_descriptor_3可以分别通过前一个地址计算得到,它们的地址是连续的**
  2. ![未命名文件 (6).jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/21860440/1629982747785-b7a7fae4-79ae-4eab-9299-527290365492.jpeg#clientId=u66de2bc7-4021-4&from=ui&id=u6af7c23a&margin=%5Bobject%20Object%5D&name=%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20%286%29.jpg&originHeight=618&originWidth=501&originalType=binary&ratio=1&size=30399&status=done&style=none&taskId=ub99186c8-fcc8-4527-a66f-9575b79d894)
  3. <a name="GjZrm"></a>
  4. ## block捕获的变量的生命周期
  5. - **继续观察上述.cpp文件,__main_block_desc_0中的数据结构即为Block_descriptor_1Block_descriptor_2**
  6. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629984290476-a9b38a62-d5ea-4b52-bff8-18e7200fbda8.png#clientId=u66de2bc7-4021-4&from=paste&height=233&id=u1c775bf9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=466&originWidth=2262&originalType=binary&ratio=1&size=148921&status=done&style=none&taskId=u001f6195-dc0a-44e5-8b07-1a4d3c10e4c&width=1131)
  7. - __main_block_copy_0实现包含了_Block_object_assign,并且包含一个8参数,其定义如下
  8. - The flags parameter of _Block_object_assign and _Block_object_dispose is set toblock对捕获变量的类型处理<br />* BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object, // 普通对象object<br />* BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and // 其它block修饰的<br />* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable. // __block修饰的
  9. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629984397357-f7dbc578-0c30-4452-a417-c492b9b9267a.png#clientId=u66de2bc7-4021-4&from=paste&height=54&id=ue9d234f7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=108&originWidth=1784&originalType=binary&ratio=1&size=61730&status=done&style=none&taskId=ua693fd59-5faa-41bb-a39e-1135b50a036&width=892)
  10. - 不同修饰,block进行的底层操作不同
  11. - BLOCK_FIELD_IS_OBJECT,仅进行指针拷贝
  12. - BLOCK_FIELD_IS_BLOCK,将对象拷贝,全新地址
  13. - BLOCK_FIELD_IS_BYREF,将对象和其指针一起拷贝,内部可修改外部(栈)的变量
  14. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1629985227375-2e62a973-3c4a-40c0-be40-3241c3893a84.png#clientId=u66de2bc7-4021-4&from=paste&height=692&id=ua9af9f2a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1384&originWidth=2142&originalType=binary&ratio=1&size=439749&status=done&style=none&taskId=ucafc3c69-4186-4b6c-a88b-2cec1261c75&width=1071)
  15. - _Block_byref_copy实现了byref_keep,即对对象进行copy,保持其生命周期
  16. - Block_byref_2包含两个参数,byref_keepbyref_destroy,在编译的.cpp文件中,分别被赋值__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131
  17. ```objectivec
  18. static struct Block_byref *_Block_byref_copy(const void *arg) {
  19. struct Block_byref *src = (struct Block_byref *)arg;
  20. // __block 内存是一样 同一个家伙
  21. //
  22. if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
  23. // src points to stack
  24. struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
  25. copy->isa = NULL;
  26. // byref value 4 is logical refcount of 2: one for caller, one for stack
  27. copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
  28. copy->forwarding = copy; // patch heap copy to point to itself
  29. src->forwarding = copy; // patch stack to point to heap copy
  30. copy->size = src->size;
  31. if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
  32. // Trust copy helper to copy everything of interest
  33. // If more than one field shows up in a byref block this is wrong XXX
  34. struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
  35. struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
  36. copy2->byref_keep = src2->byref_keep;
  37. copy2->byref_destroy = src2->byref_destroy;
  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. copy3->layout = src3->layout;
  42. }
  43. // 捕获到了外界的变量 - 内存处理 - 生命周期的保存
  44. (*src2->byref_keep)(copy, src);
  45. }
  46. else {
  47. // Bitwise copy.
  48. // This copy includes Block_byref_3, if any.
  49. memmove(copy+1, src+1, src->size - sizeof(*src));
  50. }
  51. }
  52. // already copied to heap
  53. else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
  54. latching_incr_int(&src->forwarding->flags);
  55. }
  56. return src->forwarding;
  57. }
  58. struct Block_byref_2 {
  59. // requires BLOCK_BYREF_HAS_COPY_DISPOSE
  60. BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131
  61. BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131
  62. };

image.png

  • Block_byref_id_object_copy_131和Block_byref_id_object_dispose_131实现代码
    • __Block_byref_id_object_copy_131中传入(char*)dst + 40相当于结构体内存平移,即object
    • __block修饰的对象
      1. 首先进行栈->堆copy
      2. block捕获变量,捕获当前Block_byref,对Block_byref对象进行copy
      3. __Block_byref对其内部object进行变量copy
        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. }
        4. static void __Block_byref_id_object_dispose_131(void *src) {
        5. _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
        6. }