1 介绍
在 Objective-C 中,dealloc 方法是用来释放对象资源的。当一个对象被释放时(即引用计数变为零),系统会自动调用该对象的 dealloc 方法。在 dealloc 方法中,可以执行一些清理操作,比如释放对象持有的资源、取消观察者或通知、执行其他的清理操作等。
在 Swift 中,使用 deinit 方法来执行类似的操作。当对象的引用计数降为零时,系统会自动调用该对象的 deinit 方法来执行清理操作。
2 实现机制
2.1 Dealloc 调用流程
- 首先调用
_objc_rootDealloc()
方法。
scss
- (void)dealloc {
_objc_rootDealloc(self);
}
- 接着调用
rootDealloc()
方法。
arduino
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
- 在这一步,会判断对象是否可以被释放,主要根据以下五种情况进行判断:
- NONPointer_ISA:是否为一个固定的非指针值
- weakly_reference:是否有弱引用
- has_assoc:是否有关联对象
- has_cxx_dtor:是否有 C++ 析构函数
- has_sidetable_rc:是否使用 sidetable 进行引用计数管理
sidetable
是指 Objective-C 中用于管理一些特殊情况下的引用计数的数据结构。在对象的引用计数比较大或者需要支持多线程时,Objective-C 运行时会采用sidetable
来进行引用计数的管理。sidetable
是一种哈希表的形式,用于存储额外的引用计数,这样可以更高效地管理对象的引用计数。在释放对象时,需要清理sidetable
中的相关信息,以确保内存正确释放。
- 流程处流
- 如果满足以上任意一种情况,将会调用
object_dispose()
方法进行下一步处理。 - 如果不满足上述五种情况,则执行释放操作,即调用 C 函数的
free()
。
- 执行完毕。
2.2 object_dispose() 调用流程
scss
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
- 直接调用
objc_destructInstance()
方法。 - 然后调用 C 函数
free()
进行内存释放。
2.3 objc_destructInstance() 调用流程
scss
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
- 首先判断是否有 C++ 相关内容(
hasCxxDtor
),如果有,则调用object_cxxDestruct()
方法销毁 C++ 相关内容。 - 接着判断是否有关联对象(
hasAssociatedObjects
),如果有,则调用object_remove_associations()
方法销毁关联对象。 - 调用
clearDeallocating()
方法。 - 执行完毕。
2.4 clearDeallocating() 调用流程
scss
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
- 先执行
sidetable_clearDeallocating()
方法。 - 然后执行
weak_clear_no_lock
方法,在这一步骤中,将指向该对象的弱引用指针置为 nil。 - 接着执行
table.refcnts.erase()
方法,从引用计数表中擦除该对象的引用计数。 - 至此,Dealloc 的执行流程结束。
3 开发问题
ViewController 不执行 dealloc 方法的情况?
- 循环引用,例如 block、delegate
- 未正确释放资源,例如通知、观察者、NSTimer
- 其他
- 第三方库或框架可能会影响 ViewController 的生命周期,导致 dealloc 方法不被调用。
- WKWebView 适时移除消息处理程序
removeScriptMessageHandlerForName: