聊一聊ARC下系统为什么要将栈block拷贝成堆block

网上有很多讲block底层实现的文章,里面会讲到捕获了auto修饰的局部变量的block,在MRC下是栈block,在ARC下是堆block,这篇文章只想讨论一下为什么ARC下,捕获了auto修饰的局部变量的block,系统为什么要将block从栈上拷贝到堆上。

block的底层实现

void (^resultBlock) (void);
resultBlock = ^ {
    NSLog(@"=======");
};
resultBlock();
复制代码

将.m文件转成.cpp文件,查看底层实现。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_zj_qlh1_2756d7_hcsp3s_fnrmr0000gn_T_main_accc59_mi_0);
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*resultBlock) (void);
        resultBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)resultBlock)->FuncPtr)((__block_impl *)resultBlock);
    }
    return 0;
}
复制代码
  • void (*resultBlock) (void)
先定义了一个指针变量 resultBlock (函数指针)
复制代码
  • resultBlock = ((void (*)())&__main_block_impl_0
__main_block_impl_0 是c++的构造函数,传入__main_block_func_0 和 &__main_block_desc_0_DATAl两个参数。
返回构建好的结构体变量的地址,并赋值给resultBlock
复制代码

总结一下,其实block的底层实现就是一个结构体。

一般来讲,方法中的局部变量(非调用类似alloc方法创建的)是在栈上的,方法调用完毕,系统会自动回收栈内存。
以下demo运行在模拟器上。

image.png

内存分段不同,地址区别较大。
局部变量a 地址为0x7ffee41e0ebc,内存分配在栈上,地址一般比较大
全局变量b 地址为0x10c79f168,内存在静态区
局部变量obj分配在栈区,地址为0x7ffee41e0eb0,该地址中存储的值为alloc方式创建出来的堆区变量0x600000ba4210
复制代码

image.png

再看下面这段代码:

image.png
image.png

image.png

定义一个全局区的指针变量p,在viewDidLoad中给p赋值,此时p中存储的是局部变量a的地址,a的地址是在栈上开辟的,当方法调用完毕,栈空间中的值会释放,此时如果在touchesBegan方法中访问p中的值会出现什么问题?

image.png

a的地址在栈上 : 0x7ffee2fc0eac 该地址中存储的值为10
将a的地址赋值给指针p所指向的全局区的内存空间
*p的值为10
*p的地址就是a在栈上的地址
当viewdidload 方法调用完成后,系统会释放栈空间的值,地址0x7ffeee6e5eac中存储的10,就会被释放
当在其他方法中继续访问0x7ffee2fc0eac中的值时,就会出现垃圾值32767
复制代码

同理:

image.png

resultBlock 是定义在全局区的变量
tempBlock 地址是在栈区
将tempBlock 地址中的值赋值给全局区resultBlock的地址中

复制代码

验证一下:
tempBlock 的地址 0x7ffee8657ea0

image.png

resultBlock 的地址 0x108330d20

image.png

tempBlock 和 resultBlock 中都是存储的堆block <NSMallocBlock: 0x600001fec6f0>,
本来tempBlock 是在栈上创建的,此时打印出来的地址中的值却是在堆上,说明系统帮我们做了copy操作,主要是为了延长该block生命,避免在其他方法中访问时,变量a出现垃圾值。

此时点击屏幕,调用resultBlock方法,就能正常打印a的值

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享