Day4-Block 内存管理 & 循环引用分析

YVTU

Block 内存管理 & 循环引用分析

1. Block 的结构体本质

Block 在底层其实是一个结构体:

1
2
3
4
5
6
7
8
struct Block_literal {
void *isa; // 指向 Block 类对象
int flags; // 标志位(是否有 copy/dispose 等)
int reserved; // 保留字段
void (*invoke)(void *, ...); // 函数指针,Block 实际执行逻辑
struct Block_descriptor *descriptor; // Block 描述信息
// captured variables...
};

2. 捕获外部变量时,发生了什么?

变量类型 捕获方式 内存管理行为
基本数据类型(int/float) 值拷贝 不影响内存管理
Objective-C 对象 强引用(retain) 需要注意循环引用
__block 修饰的对象 指针拷贝,但本身仍可能强引用 需配合 __weak __block

3. Block 的 copy 和 dispose 函数

Block 如果捕获了对象,底层会自动生成 copy 和 dispose 函数:

  • copy : retain 捕获的对象
  • dispose : release 捕获的对象

简化示例:

1
2
3
4
5
6
7
void _Block_copy(void *dst, void *src) {
objc_retain(capturedObj);
}

void _Block_dispose(void *src) {
objc_release(capturedObj);
}

4. 深层次的循环引用分析

常见导致 Block 循环引用的场景:

  • NSTimer / CADisplayLink / GCD
  • 异步任务 (dispatch_async)
  • 链式调用

标准防循环写法:

1
2
3
4
5
6
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
[strongSelf doSomething];
};

Block 高频面试问答

1. Block 的类型有哪些?分别什么时候出现?

答:

  • Global Block:不捕获外部变量,存储在全局区。
  • Stack Block:捕获了外部变量,存储在栈上。
  • Malloc Block:拷贝自 Stack Block,存储在堆上。

ARC 下只存在 Global Block 和 Malloc Block。


2. Block 在 ARC 下是如何内存管理的?

答:

  • ARC 会自动对 Block 执行 copy 操作,将其从栈拷贝到堆上。
  • 使用 __strong 指针持有 Block 时,默认是堆 Block。

3. 如何判断一个 Block 是否造成了循环引用?

答:

  • 检查 Block 内是否使用了 self
  • 检查 self 是否强引用了 Block(比如 Block 是 self 的属性)。
  • 使用 Instruments 的 Leaks 工具分析内存泄漏。

4. __block 修饰符在 ARC 和 MRC 下分别是什么行为?

答:

  • MRC:__block 允许修改变量,但不会自动管理引用计数。
  • ARC:__block 允许修改,但并不会弱化引用关系,需要自己处理弱引用。

5. Block 为什么容易引起内存泄漏?具体有哪些场景?

答:
因为 Block 捕获变量是强引用,特别是对象指针,如下场景常出问题:

  • UIViewController 的 Block 属性内部引用了 self
  • NSTimer、CADisplayLink、GCD 中的 Block

解决办法:

  • 使用 __weak
  • 定时器使用 weakProxy
  • GCD 注意生命周期控制

小结

理解 Block 的内存特性、引用关系变化(stack → heap)、以及在 ARC 下的默认 copy 机制,是彻底掌握 iOS 内存管理的关键。