基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

dealloc 方法的调用流程

知识点图片

在 Objective-C 中,dealloc 方法的调用流程可以分为两个层面来理解:类继承关系的调用顺序(开发者层面)和 Runtime 内部的执行流程(底层原理层面)。

以下是详细的解析:


1. 类继承关系的调用顺序 (Surface Level)

当一个对象的引用计数变为 0 时,系统会调用 dealloc。在类的继承体系中,调用顺序与 init 方法相反:

  1. 子类 (Subclass) 的 dealloc 首先被调用:先释放子类特有的资源。
  2. 父类 (Superclass) 的 dealloc 随后被调用:子类处理完后,自动向上调用父类的清理逻辑。
  3. 根类 (NSObject) 的 dealloc 最后被调用:完成最终的内存回收。

ARC 与 MRC 的区别:

  • MRC (Manual Reference Counting): 必须在 dealloc 方法的最后一行显式调用 [super dealloc]
  • ARC (Automatic Reference Counting): 严禁显式调用 [super dealloc]。编译器会自动在代码末尾插入调用父类 dealloc 的代码。

2. Runtime 内部执行流程 (Deep Dive)

这是面试或深入学习中最核心的部分。基于 Apple 开源的 ObjC Runtime 源码 (objc-runtime-new.mm),dealloc 的底层流程如下:

核心路径概览

dealloc -> _objc_rootDealloc -> rootDealloc -> (object_dispose -> objc_destructInstance -> free)

详细步骤分解

第一步:入口
对象调用 dealloc 方法。

第二步:_objc_rootDealloc
dealloc 内部直接调用 _objc_rootDealloc(self)

第三步:rootDealloc (关键判断)
这是核心逻辑所在。Runtime 会判断该对象是否利用了 Non-Pointer ISA(优化的 ISA 指针)以及是否包含特定标志位,来决定走“快速路径”还是“慢速路径”。

Runtime 会检查 isa 中的标志位:

  • nonpointer: 是否开启了指针优化。
  • weakly_referenced: 是否有弱引用指向该对象。
  • has_assoc: 是否有关联对象 (Associated Objects)。
  • has_cxx_dtor: 是否有 C++ 析构函数(ARC 下用于自动释放 ivar)。
  • has_sidetable_rc: 引用计数是否过大存储在 SideTable 中。

分支 A:快速路径 (Fast Path)
如果对象是“纯净”的(即:没有弱引用、没有关联对象、没有 C++ 析构函数、没有额外的引用计数表),Runtime 会直接调用 C 函数 free(self) 释放内存。流程结束。

分支 B:慢速路径 (Slow Path)
如果上述任一条件不满足(例如对象有关联对象或弱引用),则调用 object_dispose(self)

第四步:object_dispose
object_dispose 内部做两件事:

  1. 调用 objc_destructInstance(self) (销毁实例内容)。
  2. 调用 free(self) (释放内存块)。

第五步:objc_destructInstance (销毁实例的具体操作)
这是清理工作的核心,顺序非常重要:

  1. C++ / ARC Ivar 销毁 (object_cxxDestruct):

    • 如果有 C++ 析构函数,调用它。
    • 重点: 在 ARC 模式下,编译器会自动生成 .cxx_destruct 方法。Runtime 在这一步调用它,自动释放对象持有的所有实例变量 (ivars)。这就是为什么 ARC 下不需要手写 _ivar = nil
  2. 移除关联对象 (_object_remove_assocations):

    • 如果对象有关联对象(通过 objc_setAssociatedObject 添加的),在这里会被移除并释放。
    • 注意:关联对象的释放晚于实例变量的释放。
  3. 清除弱引用 (clearDeallocating):

    • 将指向该对象的所有 weak 指针置为 nil
    • 处理 SideTable 中的引用计数。

3. 总结图解

plaintext
obj -> dealloc
   |
   V
_objc_rootDealloc
   |
   V
rootDealloc
   |
   +-- 判断 isa 标志位 (nonpointer, weak, assoc, cxx_dtor, sidetable)
   |
   +-- [条件满足:纯净对象] ---------> free() -> 结束
   |
   +-- [条件不满足:复杂对象]
           |
           V
      object_dispose()
           |
           +-- objc_destructInstance()
           |      |
           |      +-- 1. object_cxxDestruct()  <-- ARC自动释放成员变量(ivars)
           |      |
           |      +-- 2. _object_remove_assocations() <-- 移除关联对象
           |      |
           |      +-- 3. clearDeallocating()   <-- weak指针置nil
           |
           +-- free() -> 结束

4. 开发者在 dealloc 中应该做什么?

虽然 ARC 帮我们处理了实例变量的释放,但以下工作仍需在 dealloc 中手动处理:

  1. 移除观察者
    • 移除 KVO 观察者(虽然 iOS 11+ 很多情况不需要了,但为了安全通常还是建议移除)。
    • 移除 NSNotificationCenter 监听(iOS 9+ 之后大部分情况不需要手动移除,但 block 形式的监听除外)。
  2. 停止定时器
    • 如果持有 NSTimerCADisplayLink,必须 invalidate,否则可能导致内存泄漏(循环引用)。
  3. 清理非 OC 对象
    • 释放 CoreFoundation 对象(CFRelease)。
    • 关闭文件句柄、Socket 连接等 C 语言层面的资源。
  4. 清理代理
    • 如果 delegate 是 assign 修饰的(极少见,通常是 weak),需要置为 nil,防止野指针。

总结关键点 (面试金句)

  • 调用顺序:子类 -> 父类。
  • ARC 释放成员变量的时机:在 .cxx_destruct 阶段,由 Runtime 自动调用,早于关联对象的释放和 weak 指针的清空。
  • Weak 指针置 nil 的原理:在 clearDeallocating 阶段,通过 SideTable 查找所有指向该对象的 weak 指针并置空。
00:00
00:00