OC記憶體管理--物件的生成與銷燬
原文連結 OC記憶體管理--物件的生成與銷燬
在iOS開發中了,我們每天都會使用 + alloc
和 - init
這兩個方進行物件的初始化。我們也這知道整個物件的初始化過程其實就是 開闢一塊記憶體空間,並且初始化isa_t結構體的過程 。
alloc的實現
+ (id)alloc { return _objc_rootAlloc(self); } id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } 複製程式碼
整個過程其實就是 NSObject
對 callAlloc
方法的實現。
callAlloc
/* cls:CustomClass checkNil:是否檢查Cls allocWithZone:是否分配到指定空間,預設為false,內部會對其進行優化 */ static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { //沒有class或則checkNil為YES,返回空 if (slowpath(checkNil && !cls)) return nil; //確保只有Objective-C 2.0語言的檔案所引用 #if __OBJC2__ //判斷class有沒有預設的allocWithZone方法 if (fastpath(!cls->ISA()->hasCustomAWZ())) { // class可以快速分配 if (fastpath(cls->canAllocFast())) { //hasCxxDtor();是C++解構函式,判斷是否有解構函式 bool dtor = cls->hasCxxDtor(); //申請class的記憶體空間 id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(!obj)) return callBadAllocHandler(cls); //初始化isa指標 obj->initInstanceIsa(cls, dtor); return obj; } else { //使用class_createInstance建立class id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj; } } #endif //說明有預設的allocWithZone的方法,呼叫allocWithZone方法 if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; } 複製程式碼
在 __OBJC2__
下當前類有沒有預設的 allocWithZone
方法是通過 hasCustomAWZ()
函式判斷的。 YES
代表有則會呼叫 [cls allocWithZone:nil]
方法。 NO
代表沒有,這時候會根據當前類是否可以快速分配, NO
的話呼叫 class_createInstance
函式; YES
則分配記憶體並初始化isa。
allocWithZone
+ (id)allocWithZone:(struct _NSZone *)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) { id obj; #if __OBJC2__ // allocWithZone under __OBJC2__ ignores the zone parameter (void)zone; obj = class_createInstance(cls, 0); #else if (!zone) { obj = class_createInstance(cls, 0); } else { obj = class_createInstanceFromZone(cls, 0, zone); } #endif if (slowpath(!obj)) obj = callBadAllocHandler(cls); return obj; } 複製程式碼
allocWithZone
函式的本質是呼叫 _objc_rootAllocWithZone
函式。
_objc_rootAllocWithZone
的邏輯分為兩種情況:
- 先判斷是否是
__OBJC2__
,如果是則呼叫class_createInstance
; - 判斷
zone
是否為空,如果為空呼叫class_createInstance
,如果不為空,呼叫class_createInstanceFromZone
。
//class_createInstance id class_createInstance(Class cls, size_t extraBytes) { return _class_createInstanceFromZone(cls, extraBytes, nil); } //class_createInstanceFromZone id class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) { return _class_createInstanceFromZone(cls, extraBytes, zone); } 複製程式碼
class_createInstance
和 class_createInstanceFromZone
的本質都是呼叫 _class_createInstanceFromZone
。
另外通過前面的原始碼我們可以發現: 用alloc方式建立,只要當前類有allocWithZone方法,最終一定是呼叫class_createInstance 。
_class_createInstanceFromZone
static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { if (!cls) return nil; assert(cls->isRealized()); bool hasCxxCtor = cls->hasCxxCtor();//建構函式 bool hasCxxDtor = cls->hasCxxDtor();//解構函式 bool fast = cls->canAllocNonpointer(); //是對isa的型別的區分,如果一個類不能使用isa_t型別的isa的話,fast就為false,但是在Objective-C 2.0中,大部分類都是支援的 //在分配記憶體之前,需要知道物件在記憶體中的大小,也就是instanceSize的作用。物件必須大於等於16位元組。 size_t size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (!zone&&fast) { //分配記憶體空間 obj = (id)calloc(1, size); if (!obj) return nil; //初始化isa指標 obj->initInstanceIsa(cls, hasCxxDtor); } else { //此時的fast 為 false //在C語言中,malloc表示在記憶體的動態儲存區中分配一塊長度為“size”位元組的連續區域,返回該區域的首地址;calloc表示在記憶體的動態儲存區中分配n塊長度為“size”位元組的連續區域,返回首地址。 if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (!obj) return nil; //初始化isa指標 obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj; } 複製程式碼
初始化isa
_class_createInstanceFromZone
中不光開闢了記憶體空間,還初始化了isa。初始化isa的方法有 initInstanceIsa
和 initIsa
,但是本質都是呼叫 initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
。
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { assert(!isTaggedPointer()); if (!nonpointer) { isa.cls = cls; //obj->initIsa(cls) } else { //obj->initInstanceIsa(cls, hasCxxDtor); assert(!DisableNonpointerIsa); assert(!cls->instancesRequireRawIsa()); isa_t newisa(0); #if SUPPORT_INDEXED_ISA assert(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.shiftcls = (uintptr_t)cls >> 3; #endif // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ...but not too atomic because we don't want to hurt instantiation isa = newisa; } } 複製程式碼
根據《OC引用計數器的原理》,現在再看一下初始化isa的方法。這個方法的意思是首先判斷是否開啟指標優化。
沒有開啟指標優化的話訪問 objc_object
的 isa
會直接返回 isa_t
結構中的 cls
變數, cls
變數會指向物件所屬的類的結構。
開啟指標優化的話通過 newisa(0)
函式初始化一個isa,並根據 SUPPORT_INDEXED_ISA
分別設定對應的值。iOS裝置的話這個值是0,所以執行 else
的程式碼。
到這裡alloc的實現過程已經結束了,根據上面的原始碼分析,用一張圖表示上述過程:

這裡可能會有個疑問,既然 alloc
將分配記憶體空間和初始化isa的事情都做了,那麼 init
的作用是什麼呢?
init
- (id)init { return _objc_rootInit(self); } id _objc_rootInit(id obj) { return obj; } 複製程式碼
init
的作用就是返回當前物件。這裡有個問題既然 init
只是返回當前物件,為什麼要多此一舉呢?
Apple給出的註釋:
In practice, it will be hard to rely on this function. Many classes do not properly chain -init calls.
意思是在實踐中,很難依靠這個功能。許多類沒有正確連結 init
呼叫。所以這個函式很可能不被呼叫。也許是歷史遺留問題吧。
new
+ (id)new { return [callAlloc(self, false/*checkNil*/) init]; } 複製程式碼
所以說 UIView *view = [UIView new];
和 UIView *view = [[UIView alloc]init];
是一樣的。
dealloc
分析了物件的生成,我們現在看一下物件是如何被銷燬的。 dealloc
的實現如下:
- (void)dealloc { _objc_rootDealloc(self); } void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } inline void objc_object::rootDealloc() { if (isTaggedPointer()) return;// fixme necessary? 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); } } 複製程式碼
rootDealloc
分為三種情況:
object_dispose
objc_destructInstance
我們先看 object_dispose
函式的原始碼:
id object_dispose(id obj) { if (!obj) return nil; objc_destructInstance(obj); free(obj); return nil; } 複製程式碼
做了兩件事情:
objc_destructInstance
objc_destructInstance
的實現如下:
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. **********************************************************************/ 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);//刪除關聯物件 obj->clearDeallocating();//清空引用計數表並清除弱引用表 } return obj; } 複製程式碼
objc_destructInstance
做了三件事情:
object_cxxDestruct _object_remove_assocations clearDeallocating
object_cxxDestruct
在原始碼中 object_cxxDestruct
的實現由 object_cxxDestructFromClass
完成。
static void object_cxxDestructFromClass(id obj, Class cls) { void (*dtor)(id); // Call cls's dtor first, then superclasses's dtors. for ( ; cls; cls = cls->superclass) { if (!cls->hasCxxDtor()) return; dtor = (void(*)(id)) lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct); if (dtor != (void(*)(id))_objc_msgForward_impcache) { if (PrintCxxCtors) { _objc_inform("CXX: calling C++ destructors for class %s", cls->nameForLogging()); } (*dtor)(obj); } } } 複製程式碼
這段程式碼的意思就是沿著繼承鏈逐層向上搜尋 SEL_cxx_destruct
這個 selector
,找到函式實現(void (*)(id)(函式指標)並執行。說白了就是找解構函式,並執行解構函式。
解構函式中書如何處理成員變數的?
objc_storeStrong(&ivar, nil) objc_destroyWeak(&ivar)
關於這個函式 Sunnyxx ARC下dealloc過程及.cxx_destruct的探究 中也有提到。
用一張圖表示 dealloc
的流程:

至於 dealloc
的呼叫時機,是跟引用計數器相關的。