5-9.【OC】【Block】Block 捕获 self 的真实语义是什么?

2 阅读2分钟

在 Objective-C 中,Block 捕获 self 的真实语义可以概括为一句话:它在 Block 的结构体内部创建了一个强引用指针,指向了 self 所代表的对象。

这里有三个核心层面的含义,直接决定了你会不会写出 Bug 或内存泄漏。


1. 结构体层面的“成员变量”

从底层内存布局来看,当你进入 Block 的代码块并使用了 self(无论是直接使用 self 还是访问成员变量 _property),编译器就会在 Block 的 Captured Variables 区域添加一个字段。

  • 字段类型: id __strong self;
  • 赋值时机: 在 Block 创建时,它会将当前的 self 指针赋值给这个内部字段。
  • 语义: 这是一次显式的强引用。Block 的引用计数增加不会影响 self,但 self 的引用计数会因为被 Block 持有而加 1。

2. “捕获 self”与“捕获成员变量”的等价性

这是一个最容易产生误解的语义。在 Block 里使用 _name 和使用 self.name 实际上都会捕获 self

Objective-C

// 场景 A
self.block = ^{
    NSLog(@"%@", _name); // 隐式捕获 self
};

// 场景 B
self.block = ^{
    NSLog(@"%@", self.name); // 显式捕获 self
};
  • 真实语义: 成员变量(Ivar)是存储在 self 对象的内存空间里的。要访问 _name,Block 必须先通过 self 指针找到对象的基地址,再加上偏移量。因此,访问成员变量等同于强引用整个 self

3. 内存闭环(循环引用)的成因

为什么捕获 self 经常导致 Retain Cycle

  1. self 拥有(持有)一个 block 属性。
  2. block 被创建并拷贝到堆上。
  3. 根据捕获语义,block 内部结构体持有了 self 的强引用。
  4. 结果: selfblockself。双方都在等对方释放,导致谁也无法销毁。

4. 解决语义:__weak 的介入

当我们使用 __weak typeof(self) weakSelf = self; 时,语义发生了变化:

  • Block 的内部结构体字段变成了 id __weak weakSelf;
  • 真实语义: Block 不再增加 self 的引用计数。它仅仅记录了 self 的地址。如果 self 被销毁,这个内部指针会自动置为 nil

总结:真实语义清单

  • 强持有: 只要 Block 不销毁,self 就一定不会被销毁。
  • 全对象捕获: 捕获 self 的任何一部分(属性、变量、方法)都等同于捕获整个 self 对象。
  • 延迟释放: 如果 Block 被放到了后台队列(如 GCD),它会延长 self 的生命周期,直到任务执行完毕。