5-6.【OC】【Block】__block 修饰符的底层原理是什么?

4 阅读2分钟

简单来说,__block 修饰符的作用是**“将一个栈上的变量包装成一个对象”**,从而让它能够随着 Block 一起被拷贝到堆上,实现跨作用域的读写访问。

如果不加 __block,Block 捕获局部变量只是值传递(将其值拷贝到 Block 的结构体成员中);加上 __block 后,就变成了引用传递的底层实现。


1. 底层结构:从“基本类型”到“结构体对象”

当你给一个变量加上 __block 时,编译器会将该变量重构为一个结构体:Block_byref

__block int age = 10; 为例,底层会转化为类似这样的结构:

C++

struct __Block_byref_age_0 {
    void *__isa;                          // 对象指针
    struct __Block_byref_age_0 *__forwarding; // 关键:指向自身的指针
    int __flags;
    int __size;
    int age;                              // 实际存储的变量值
};

此时,你在代码中对 age 的读写,实际上都会变成 age->__forwarding->age


2. 关键机制:__forwarding 指针

这是 __block 最精妙的设计,解决了栈和堆共存时的同步问题。

  1. 在栈上时: __forwarding 指向变量自身(栈上的结构体)。
  2. 拷贝到堆时: 当 Block 从栈拷贝到堆,__block 变量也会被拷贝到堆。此时,栈上的 __forwarding 指向堆上的副本,而堆上的 __forwarding 指向它自己

为什么要这么做?

这样无论你在原来的函数里(访问栈变量)还是在异步 Block 里(访问堆变量)修改 age,最终操作的都是堆上的同一份数据。


3. 内存管理:Copy 与 Retain

当 Block 执行 copy 操作时,底层会调用 _Block_object_assign 函数。对于 __block 变量:

  • 它会触发 __Block_byref 结构体的拷贝。
  • 增加该结构体的引用计数。
  • 如果多个 Block 捕获了同一个 __block 变量,它们会共同持有堆上的那一份,直到所有 Block 都销毁,变量才会被释放。

4. 总结:加与不加的区别

特性普通捕获 (const copy)__block 捕获
底层实现直接拷贝变量的值包装成 Block_byref 结构体
可修改性Block 内部不可修改(只读)Block 内部可修改
内存位置随 Block 拷贝而拷贝值独立包装,支持 __forwarding 转发
解决场景仅需获取快照需要在 Block 内部改变外部变量

一个有趣的细节

MRC 下,__block 还可以用来避免循环引用(因为它不会 retain 捕获的对象);但在 ARC 下,__block 会对对象产生强引用,所以 ARC 下解决循环引用必须用 __weak