OC內(nèi)存管理--對(duì)象的生成與銷毀

原文鏈接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的邏輯分為兩種情況:

  1. 先判斷是否是__OBJC2__,如果是則調(diào)用class_createInstance沈撞;
  2. 判斷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_createInstanceclass_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的方法有initInstanceIsainitIsa,但是本質(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_objectisa會(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的代碼胧洒。

到這里alloc的實(shí)現(xiàn)過程已經(jīng)結(jié)束了畏吓,根據(jù)上面的源碼分析,用一張圖表示上述過程:
image

這里可能會(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分為三種情況:

  1. 如果是TaggedPointer,直接return感耙;
  2. 進(jìn)行一些關(guān)于isa的條件判斷褂乍,如果滿足就釋放分配的內(nèi)存控件;
  3. 調(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;
}

做了兩件事情:

  1. 調(diào)用objc_destructInstance函數(shù)
  2. 釋放分配的內(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做了三件事情:

  1. 執(zhí)行object_cxxDestruct調(diào)用析構(gòu)函數(shù)
  2. 執(zhí)行_object_remove_assocations刪除關(guān)聯(lián)對(duì)象
  3. 執(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ù)中書如何處理成員變量的哥艇?

  1. 對(duì)于strong來說執(zhí)行objc_storeStrong(&ivar, nil)release舊對(duì)象,ivar賦新值nil僻澎;
  2. 對(duì)于weak來說執(zhí)行objc_destroyWeak(&ivar)消除對(duì)象weak表中的ivar地址她奥。

關(guān)于這個(gè)函數(shù)Sunnyxx ARC下dealloc過程及.cxx_destruct的探究中也有提到。

用一張圖表示dealloc的流程:

image

至于dealloc的調(diào)用時(shí)機(jī)怎棱,是跟引用計(jì)數(shù)器相關(guān)的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绷跑,一起剝皮案震驚了整個(gè)濱河市拳恋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砸捏,老刑警劉巖谬运,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異垦藏,居然都是意外死亡梆暖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門掂骏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轰驳,“玉大人,你說我怎么就攤上這事弟灼〖督猓” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵田绑,是天一觀的道長(zhǎng)勤哗。 經(jīng)常有香客問我,道長(zhǎng)掩驱,這世上最難降的妖魔是什么芒划? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮欧穴,結(jié)果婚禮上民逼,老公的妹妹穿的比我還像新娘。我一直安慰自己苔可,他們只是感情好缴挖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著焚辅,像睡著了一般映屋。 火紅的嫁衣襯著肌膚如雪苟鸯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天棚点,我揣著相機(jī)與錄音早处,去河邊找鬼。 笑死瘫析,一個(gè)胖子當(dāng)著我的面吹牛砌梆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贬循,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼咸包,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了杖虾?” 一聲冷哼從身側(cè)響起烂瘫,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奇适,沒想到半個(gè)月后坟比,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嚷往,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年葛账,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皮仁。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡籍琳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贷祈,到底是詐尸還是另有隱情巩割,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布付燥,位于F島的核電站宣谈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏键科。R本人自食惡果不足惜闻丑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勋颖。 院中可真熱鬧嗦嗡,春花似錦、人聲如沸饭玲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矮冬,卻和暖如春谈宛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胎署。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工吆录, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人琼牧。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓恢筝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親巨坊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撬槽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容