找回密码
 立即注册
首页 业界区 业界 Block 内存布局详解

Block 内存布局详解

涂流如 昨天 22:10
1 内存布局

按照LLVM工程源码中的Block-ABI-Apple.rst描述,Block的内存布局如下:
  1. struct Block {
  2.     void *isa;
  3.     int flags;
  4.     int reserved;
  5.     R(*invoke)(Block *, ...);
  6.     struct Block_Descriptor {
  7.         unsigned long int reserved;
  8.         unsigned long int size;
  9.         void(*copy_helper)(void *dst, void *src);
  10.         void(*dispose_helper)(void *src);
  11.     } *descriptor;
  12.     // 被捕获的变量
  13.     ...
  14. }
复制代码
isa表明Block也是一个OC对象,它的取值后面会说明。
flags是各种标志位,它的取值后面会说明。
reserved是保留字段,不赋值。
invoke是函数指针,指向Block要执行的函数。
descriptor是一个结构体指针,里面包含了Block的各种描述信息。
descriptor.reserved是保留字段,不会进行赋值。
descriptor.size是整个Block结构体的大小。
descriptor.copy_helper与Block的拷贝相关,这个成员只有满足特定条件才会存在,后面会有介绍。
descriptor.dispose_helper与Block的释放相关,这个成员只有满足特定条件才会存在,后面会有介绍。
descriptor后面就是Block捕获的各种变量。
1.png

下面用一个例子来直观感受一下,假设有下面的Block定义:
  1. int bi = 4;
  2. void(^blk)(int, int, int) = ^(int i, int j, int k) {
  3.     int result = i + j + k + bi;
  4.     NSLog(@"%ld", result);
  5. };
复制代码
使用lldb查看内存布局如下:
  1. (lldb) po $x0
  2. <__NSStackBlock__: 0x16ba0f280>
  3. signature: "v20@?0i8i12i16"
  4. invoke   : 0x1043eff4c (~/Library/Developer/CoreSimulator/Devices/ABFDFFF1-D158-48E1-9C91-0C8642E93E82/data/Containers/Bundle/Application/FC608AF0-55EA-493E-A1D4-851CFD67F9F0/iOSTest.app/iOSTest`__22-[BlockHandler handle]_block_invoke)
复制代码
可以看到上面的Block是一个__NSStackBlock,地址是0x16ba0f280。
下面看下地址0x16ba0f280对应的内存值:
  1. (lldb) x/8g 0x16ba0f280
  2. 0x16ba0f280: 0x00000001f2d7bb28 0x00000000c0000000
  3. 0x16ba0f290: 0x00000001043eff4c 0x00000001043fc220
  4. 0x16ba0f2a0: 0x0000000000000004
复制代码
按照上面所述的内存布局:
0x00000001f2d7bb28就是isa指针。
0x00000000c0000000就是reserved+flags,高4字节是reserved,低4字节是flags。
0x00000001043eff4c就是invoke指针。
0x00000001043fc220就是descriptor指针。
0x0000000000000004就是捕获的变量bi,它的值是4。
我们分别将它们的值打印出来确认一下:
  1. # isa
  2. (lldb) po 0x00000001f2d7bb28
  3. __NSStackBlock__
  4. # invoke
  5. (lldb) image lookup -a 0x00000001043eff4c
  6.       Address: iOSTest[0x0000000100003f4c] (iOSTest.__TEXT.__text + 12020)
  7.       Summary: iOSTest`__22-[BlockHandler handle]_block_invoke at BlockHandler.m:16
  8.       
  9. # descriptor 指针
  10. (lldb) image lookup -a 0x00000001043fc220
  11.       Address: iOSTest[0x0000000100010220] (iOSTest.__DATA_CONST.__const + 0)
  12.       Summary: iOSTest`__block_descriptor_36_e14_v20?0i8i12i16l
复制代码
descriptor结构体的内存值也可以打印出来:
  1. (lldb) x/8g 0x00000001043fc220
  2. 0x1043fc220: 0x0000000000000000 0x0000000000000024
复制代码
可以看到第1个8字节是reserved字段,没有赋值,保持0。
第2个8字节是size字段,表示整个Block结构体占用0x24个字节,换算成10进制就是36字节,捕获的int变量只占用了4字节。
由于不满足条件,descriptor结构体没有copy_helper和dispose_helper。
2 isa

Block结构体最顶部是isa指针,说明也可以看成一个OC对象。
Block的类型可以有以下3种:
StackBlock: 创建在栈上的Block;
GlobalBlock: 全局的Block;
MallocBlock: 创建在堆上的Block。
但是,上面所写的Block例子:
  1. int bi = 4;
  2. void(^blk)(int, int, int) = ^(int i, int j, int k) {
  3.     int result = i + j + k + bi;
  4.     NSLog(@"%ld", result);
  5. };
复制代码
看起来应该是一个StackBlock,但是如果在lldb上打印blk,会发现它是一个MallocBlock:
  1. (lldb) po [blk description]
  2. <__NSMallocBlock__: 0x600000c1c1b0>
复制代码
原因是在ARC环境下,编译器自动将创建出来的StackBlock进行了Retain操作,导致变成了MallocBlock:
  1. ...
  2. 0x104b7bebc <+112>: add    x8, x8, #0x220            ; __block_descriptor_36_e14_v20?0i8i12i16l
  3. 0x104b7bec0 <+116>: str    x8, [sp, #0x48]
  4. 0x104b7bec4 <+120>: ldur   w8, [x29, #-0x14]
  5. 0x104b7bec8 <+124>: str    w8, [sp, #0x50]
  6. ->  0x104b7becc <+128>: bl     0x104b81278               ; symbol stub for: objc_retainBlock
复制代码
如果想查看StackBlock,就要在Retain之前,像上面一样打印$x0寄存器。
同时,并不仅仅是定义在全局环境下的Block才能成为GlobalBlock。
满足下面2个条件,也可以成为GlobalBlock:
1 定义的Block不捕获任何变量;
2 Block内部只使用全局变量或者static变量。
也就是说,下面定义的Block都是GlobalBlock:
  1. void blockTest() {
  2.   // 没有捕获任何变量
  3.   void(^blk)(int, int, int) = ^(int i, int j, int k) {
  4.       int result = i + j + k;
  5.       NSLog(@"%ld", result);
  6.   };
  7. }
  8. int g = 1; // 全局变量
  9. void blockTest() {
  10.   // 只使用全局变量
  11.   void(^blk)(int, int, int) = ^(int i, int j, int k) {
  12.       int result = i + j + k + g;
  13.       NSLog(@"%ld", result);
  14.   };
  15. }
  16. static int s = 1; // 静态变量
  17. void blockTest() {
  18.   // 只使用静态变量
  19.   void(^blk)(int, int, int) = ^(int i, int j, int k) {
  20.       int result = i + j + k + s;
  21.       NSLog(@"%ld", result);
  22.   };
  23. }
  24. void blockTest() {
  25.   static int s = 1; // 局部静态变量
  26.   // 只使用局部静态变量
  27.   void(^blk)(int, int, int) = ^(int i, int j, int k) {
  28.       int result = i + j + k + s;
  29.       NSLog(@"%ld", result);
  30.   };
  31. }
复制代码
3 flags

在LLVM工程源码中的CGBlocks.h中定义了flags:
[code]enum BlockLiteralFlags {  BLOCK_IS_NOESCAPE      =  (1

相关推荐

您需要登录后才可以回帖 登录 | 立即注册