今天来聊聊 Dealloc,它的实现机制是内存管理部分的重点,把这个知识点弄明白,对于全方位的理解 iOS 内存管理非常有帮助。本文将从源码的角度来解析 Dealloc 的实现机制。
本文源码可在下列位置中找到
runtime/objc-runtime-new.mm/
runtime/objc-object.h
runtime/NSObject.mm
runtime/objc-weak.mm
调用流程 调用流程图如下
Dealloc的调用流程概括来讲大概有5个基本步骤:
1 2 3 - (void )dealloc { _objc_rootDealloc(self ); }
1 2 3 4 5 6 7 void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 inline void objc_object::rootDealloc() { if (isTaggedPointer()) return ; if (fastpath(isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc)) { assert(!sidetable_present()); free(this ); } else { object_dispose((id )this ); } }
object_dispose() 调用流程
直接调用 objc_destructInstance()
之后会调用 C函数的 free()
进行释放。
1 2 3 4 5 6 7 8 9 10 11 id object_dispose(id obj) { if (!obj) return nil ; objc_destructInstance(obj); free(obj); return nil ; }
objc_destructInstance() 调用流程
先判断 hasCxxDtor
,如果有 C++
的相关内容,要调用 object_cxxDestruct()
,销毁 C++
相关的内容。
再判断 hasAssocitatedObjects
,如果有的话,要调用 object_remove_associations()
,销毁关联对象的一系列操作。
然后调用 clearDeallocating()
。
执行完毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void *objc_destructInstance(id obj) { if (obj) { bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); } return obj; }
clearDeallocating() 调用流程
先执行 sideTable_clearDellocating()
。
再执行 weak_clear_no_lock,
在这一步骤中,会将指向该对象的弱引用指针置为 nil
。
接下来执行 table.refcnts.eraser()
,从引用计数表中擦除该对象的引用计数。
至此为止,Dealloc
的执行流程结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 inline void objc_object::clearDeallocating() { if (slowpath(!isa.nonpointer)) { sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { clearDeallocating_slow(); } assert(!sidetable_present()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void objc_object::sidetable_clearDeallocating() { SideTable& table = SideTables()[this ]; table.lock(); RefcountMap::iterator it = table.refcnts.find(this ); if (it != table.refcnts.end()) { if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) { weak_clear_no_lock(&table.weak_table, (id )this ); } table.refcnts.erase(it); } table.unlock(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); if (entry == nil ) { return ; } weak_referrer_t *referrers; size_t count; if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0 ; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil ; } else if (*referrer) { _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n" , referrer, (void *)*referrer, (void *)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this ]; table.lock(); if (isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id )this ); } if (isa.has_sidetable_rc) { table.refcnts.erase(this ); } table.unlock(); }
调用时机 我们可以从sidetable_release
函数的实现来窥出端倪,它会在给对象发送release
消息的时候调用,sidetable_release
方法首先获取对象的引用计数,对引用计数相关标志位做操作,若对象实例可以被释放,将通过objc_msgSend
发送SEL_dealloc
消息(调用对象的dealloc
方法)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 uintptr_t objc_object::sidetable_release(bool performDealloc) { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this ]; bool do_dealloc = false ; table.lock(); RefcountMap::iterator it = table.refcnts.find(this ); if (it == table.refcnts.end()) { do_dealloc = true ; table.refcnts[this ] = SIDE_TABLE_DEALLOCATING; } else if (it->second < SIDE_TABLE_DEALLOCATING) { do_dealloc = true ; it->second |= SIDE_TABLE_DEALLOCATING; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) { ((void (*)(objc_object *, SEL))objc_msgSend)(this , SEL_dealloc); } return do_dealloc; }
从sidetable_release 源码中,我们可得知,其调用线程为最后一个调用release
方法的线程,当需要释放对象时,向对象实例发送SEL_dealloc
(即dealloc
)消息。
也就是说,dealloc
并不总是在主线程中被调用,它有可能在任何线程被调用,这就需要注意一点,就是在dealloc
中进行UIKit
相关API
的操作(UIKit
相关API
只能在主线程操作)。