原文鏈接OC內(nèi)存管理--對(duì)象的生成與銷毀
在iOS開發(fā)中了,我們每天都會(huì)使用+ alloc
和- init
這兩個(gè)方進(jìn)行對(duì)象的初始化极颓。我們也這知道整個(gè)對(duì)象的初始化過程其實(shí)就是開辟一塊內(nèi)存空間短蜕,并且初始化isa_t結(jié)構(gòu)體的過程近零。
alloc的實(shí)現(xiàn)
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls) {
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
整個(gè)過程其實(shí)就是NSObject
對(duì)callAlloc
方法的實(shí)現(xiàn)蹋宦。
callAlloc
/*
cls:CustomClass
checkNil:是否檢查Cls
allocWithZone:是否分配到指定空間修档,默認(rèn)為false顾孽,內(nèi)部會(huì)對(duì)其進(jìn)行優(yōu)化
*/
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有沒有默認(rèn)的allocWithZone方法
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// class可以快速分配
if (fastpath(cls->canAllocFast())) {
//hasCxxDtor();是C++析構(gòu)函數(shù),判斷是否有析構(gòu)函數(shù)
bool dtor = cls->hasCxxDtor();
//申請(qǐng)class的內(nèi)存空間
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
//初始化isa指針
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
//使用class_createInstance創(chuàng)建class
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
//說明有默認(rèn)的allocWithZone的方法若厚,調(diào)用allocWithZone方法
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
在__OBJC2__
下當(dāng)前類有沒有默認(rèn)的allocWithZone
方法是通過hasCustomAWZ()
函數(shù)判斷的拦英。YES
代表有則會(huì)調(diào)用[cls allocWithZone:nil]
方法。NO
代表沒有测秸,這時(shí)候會(huì)根據(jù)當(dāng)前類是否可以快速分配疤估,NO
的話調(diào)用class_createInstance
函數(shù);YES
則分配內(nèi)存并初始化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
函數(shù)的本質(zhì)是調(diào)用_objc_rootAllocWithZone
函數(shù)铃拇。
_objc_rootAllocWithZone
的邏輯分為兩種情況:
- 先判斷是否是
__OBJC2__
,如果是則調(diào)用class_createInstance
沈撞; - 判斷
zone
是否為空慷荔,如果為空調(diào)用class_createInstance
,如果不為空缠俺,調(diào)用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
的本質(zhì)都是調(diào)用_class_createInstanceFromZone
。
另外通過前面的源代碼我們可以發(fā)現(xiàn):用alloc方式創(chuàng)建壹士,只要當(dāng)前類有allocWithZone方法磷雇,最終一定是調(diào)用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();//構(gòu)造函數(shù)
bool hasCxxDtor = cls->hasCxxDtor();//析構(gòu)函數(shù)
bool fast = cls->canAllocNonpointer(); //是對(duì)isa的類型的區(qū)分墓卦,如果一個(gè)類不能使用isa_t類型的isa的話倦春,fast就為false,但是在Objective-C 2.0中落剪,大部分類都是支持的
//在分配內(nèi)存之前睁本,需要知道對(duì)象在內(nèi)存中的大小,也就是instanceSize的作用忠怖。對(duì)象必須大于等于16字節(jié)呢堰。
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
//分配內(nèi)存空間
obj = (id)calloc(1, size);
if (!obj) return nil;
//初始化isa指針
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
//此時(shí)的fast 為 false
//在C語言中,malloc表示在內(nèi)存的動(dòng)態(tài)存儲(chǔ)區(qū)中分配一塊長(zhǎng)度為“size”字節(jié)的連續(xù)區(qū)域凡泣,返回該區(qū)域的首地址枉疼;calloc表示在內(nèi)存的動(dòng)態(tài)存儲(chǔ)區(qū)中分配n塊長(zhǎng)度為“size”字節(jié)的連續(xù)區(qū)域皮假,返回首地址。
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
中不光開辟了內(nèi)存空間骂维,還初始化了isa惹资。初始化isa的方法有initInstanceIsa
和initIsa
,但是本質(zhì)都是調(diào)用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;
}
}
根據(jù)《OC引用計(jì)數(shù)器的原理》褪测,現(xiàn)在再看一下初始化isa的方法。這個(gè)方法的意思是首先判斷是否開啟指針優(yōu)化潦刃。
沒有開啟指針優(yōu)化的話訪問 objc_object
的isa
會(huì)直接返回isa_t
結(jié)構(gòu)中的cls
變量侮措,cls
變量會(huì)指向?qū)ο笏鶎俚念惖慕Y(jié)構(gòu)。
開啟指針優(yōu)化的話通過newisa(0)
函數(shù)初始化一個(gè)isa乖杠,并根據(jù)SUPPORT_INDEXED_ISA
分別設(shè)置對(duì)應(yīng)的值分扎。iOS設(shè)備的話這個(gè)值是0,所以執(zhí)行else
的代碼胧洒。
這里可能會(huì)有個(gè)疑問略荡,既然alloc
將分配內(nèi)存空間和初始化isa的事情都做了庵佣,那么init
的作用是什么呢?
init
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj) {
return obj;
}
init
的作用就是返回當(dāng)前對(duì)象汛兜。這里有個(gè)問題既然init
只是返回當(dāng)前對(duì)象巴粪,為什么要多此一舉呢?
Apple給出的注釋:
In practice, it will be hard to rely on this function. Many classes do not properly chain -init calls.
意思是在實(shí)踐中粥谬,很難依靠這個(gè)功能肛根。許多類沒有正確鏈接init
調(diào)用。所以這個(gè)函數(shù)很可能不被調(diào)用漏策。也許是歷史遺留問題吧派哲。
new
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
所以說UIView *view = [UIView new];
和UIView *view = [[UIView alloc]init];
是一樣的。
dealloc
分析了對(duì)象的生成掺喻,我們現(xiàn)在看一下對(duì)象是如何被銷毀的芭届。dealloc
的實(shí)現(xiàn)如下:
- (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
分為三種情況:
- 如果是TaggedPointer,直接return感耙;
- 進(jìn)行一些關(guān)于isa的條件判斷褂乍,如果滿足就釋放分配的內(nèi)存控件;
- 調(diào)用
object_dispose
函數(shù)即硼,這是最重要的逃片;
objc_destructInstance
我們先看object_dispose
函數(shù)的源碼:
id object_dispose(id obj) {
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
做了兩件事情:
- 調(diào)用
objc_destructInstance
函數(shù) - 釋放分配的內(nèi)存空間
objc_destructInstance
的實(shí)現(xiàn)如下:
/***********************************************************************
* 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();//是否有析構(gòu)函數(shù)
bool assoc = obj->hasAssociatedObjects();//是否有關(guān)聯(lián)對(duì)象
// This order is important.
if (cxx) object_cxxDestruct(obj);//調(diào)用析構(gòu)函數(shù)
if (assoc) _object_remove_assocations(obj);//刪除關(guān)聯(lián)對(duì)象
obj->clearDeallocating();//清空引用計(jì)數(shù)表并清除弱引用表
}
return obj;
}
objc_destructInstance
做了三件事情:
- 執(zhí)行
object_cxxDestruct
調(diào)用析構(gòu)函數(shù) - 執(zhí)行
_object_remove_assocations
刪除關(guān)聯(lián)對(duì)象 - 執(zhí)行
clearDeallocating
清空引用計(jì)數(shù)表并清除弱引用表,將所有weak引用指nil(這也解釋了為什么使用weak能自動(dòng)置空)
object_cxxDestruct
在源碼中object_cxxDestruct
的實(shí)現(xiàn)由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
這個(gè)selector
褥实,找到函數(shù)實(shí)現(xiàn)(void (*)(id)(函數(shù)指針)并執(zhí)行呀狼。說白了就是找析構(gòu)函數(shù),并執(zhí)行析構(gòu)函數(shù)损离。
析構(gòu)函數(shù)中書如何處理成員變量的哥艇?
- 對(duì)于strong來說執(zhí)行
objc_storeStrong(&ivar, nil)
release舊對(duì)象,ivar賦新值nil僻澎; - 對(duì)于weak來說執(zhí)行
objc_destroyWeak(&ivar)
消除對(duì)象weak表中的ivar地址她奥。
關(guān)于這個(gè)函數(shù)Sunnyxx ARC下dealloc過程及.cxx_destruct的探究中也有提到。
用一張圖表示dealloc
的流程:
至于dealloc
的調(diào)用時(shí)機(jī)怎棱,是跟引用計(jì)數(shù)器相關(guān)的。