OC源碼 —— alloc, init, new和dealloc

上一篇最后講release的時(shí)候說(shuō)到靶溜,在release的最后淮腾,當(dāng)引用計(jì)數(shù)減為0的時(shí)候就進(jìn)入了dealloc的過程鸯屿。這一篇就來(lái)講講dealloc和相關(guān)的一些方法止潮。先從dealloc的對(duì)頭alloc說(shuō)起般哼。


alloc

關(guān)于alloc吴汪,最常見的用法應(yīng)該算是:

[[XXClass alloc] init];

方法alloc的調(diào)用棧是這樣的:

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    return class_createInstance(cls, 0);
}

id  class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    size_t size = cls->instanceSize(extraBytes);
    id obj = (id)calloc(1, size);
    obj->initInstanceIsa(cls, hasCxxDtor);
    return obj;
}

在深入alloc方法的時(shí)候,我發(fā)現(xiàn)alloc方法的調(diào)用棧很深蒸眠,但是做的事情其實(shí)不多漾橙,中間很多方法看起來(lái)一大段,其實(shí)真正執(zhí)行的只有一小部分楞卡,所以我在展示源代碼的時(shí)候只保留了最常用的代碼霜运。

下面就來(lái)分析一下alloc方法最深處的_class_createInstanceFromZone()方法脾歇。

  • size_t size = cls->instanceSize(extraBytes);
size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}

uint32_t alignedInstanceSize() {
  return word_align(unalignedInstanceSize());
}

uint32_t unalignedInstanceSize() {
    return data()->ro->instanceSize;
}

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
#   define WORD_MASK 7UL

第一步獲取對(duì)象的大小,注釋已經(jīng)講的很清楚淘捡,對(duì)象大小最小為16藕各。并且word_align()方法確保大小是8的倍數(shù),其實(shí)就是按字節(jié)對(duì)齊焦除。

  • id obj = (id)calloc(1, size);
    懂一點(diǎn)c的人都明白激况,這是分配一塊大小為size的內(nèi)存,并返回指向起始地址的指針膘魄。

  • obj->initInstanceIsa(cls, hasCxxDtor);
    初始化isa乌逐,這一部分的源代碼在Runtime源碼 —— 對(duì)象、類和isa這篇文章中已經(jīng)講過了创葡,這里就不說(shuō)了浙踢。

  • return obj;
    最后返回初始化好的obj。

總的來(lái)說(shuō)alloc方法還是很簡(jiǎn)單的灿渴。


init

init方法更簡(jiǎn)單:

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}

簡(jiǎn)單到什么都沒做洛波。

看到這里,我就奇怪了逻杖,這init什么都不做奋岁,還要調(diào)用了干什么,只要用一個(gè)alloc不就夠了么荸百,我測(cè)試了一下:

alloc方法測(cè)試.png

只調(diào)用alloc闻伶,可以設(shè)置屬性,可以調(diào)用方法够话,看起來(lái)我們習(xí)慣的寫法是遠(yuǎn)古遺留的產(chǎn)物蓝翰。


new

現(xiàn)在其實(shí)這么寫的已經(jīng)不多了:

[[XXClass alloc] init];

大多數(shù)都用一個(gè)new代替了:

[XXClass new];

這個(gè)方法也很簡(jiǎn)單:

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

其實(shí)就是把a(bǔ)lloc和init結(jié)合起來(lái)了,不過現(xiàn)在init什么事都不做女嘲,new和alloc方法其實(shí)是一個(gè)意思了畜份。


dealloc

自從進(jìn)入ARC時(shí)代,dealloc方法已經(jīng)不常見了欣尼,除非有CF對(duì)象需要釋放爆雹。

本文開始的時(shí)候也提到過,在release方法最后愕鼓,當(dāng)引用計(jì)數(shù)降為0的時(shí)候钙态,會(huì)調(diào)用dealloc方法釋放內(nèi)存」交危看看dealloc方法究竟做了什么事册倒。

- (void)dealloc {
    _objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj)
{
    obj->rootDealloc();
}

inline void objc_object::rootDealloc()
{
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

在最后的rootDealloc()方法中,有個(gè)if判斷磺送,條件分為5個(gè)部分:

  1. isa.nonpointer
    是否是nonpointer驻子,基本都是
  2. !isa.weakly_referenced
    是否被弱引用
  3. !isa.has_assoc
    是否有關(guān)聯(lián)對(duì)象
  4. !isa.has_cxx_dtor
    是否有c++析構(gòu)器
  5. !isa.has_sidetable_rc
    引用計(jì)數(shù)是否曾經(jīng)溢出過

如果以上判斷條件都為真灿意,那么可以快速釋放對(duì)象,這也是為什么在Runtime源碼 —— 對(duì)象崇呵、類和isa這篇文章中介紹isa結(jié)構(gòu)體相關(guān)字段時(shí)提到過的可以快速釋放對(duì)象缤剧,就是在這里知道的。

那么大多數(shù)情況是什么樣的呢演熟?大多數(shù)情況這個(gè)判斷都是false鞭执,因?yàn)榈?點(diǎn),只需要類中聲明過屬性或者實(shí)例變量芒粹,那么就需要c++析構(gòu)函數(shù)來(lái)釋放這些ivars。所以想要快速釋放大溜,要求還挺高化漆。

那就看看慢速釋放的過程吧:

object_dispose((id)this);

id object_dispose(id obj)
{
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

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;
}

釋放對(duì)象的時(shí)機(jī)是在objc_destructInstance()方法調(diào)用之后,這個(gè)方法做的事情是銷毀這個(gè)對(duì)象存在的證據(jù)钦奋,分成3個(gè)部分銷毀:

  • object_cxxDestruct(obj);
  • _object_remove_assocations(obj);
  • obj->clearDeallocating();
part1
object_cxxDestruct(obj);

void object_cxxDestruct(id obj)
{
    object_cxxDestructFromClass(obj, obj->ISA());
}

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) {
            (*dtor)(obj);
        }
    }
}

學(xué)過c++的都知道析構(gòu)函數(shù)從子類開始座云,逐層向上調(diào)用。這個(gè)方法其實(shí)就做了這個(gè)事情付材,有一點(diǎn)奇怪的就是朦拖,在我們的類中,并沒有聲明過任何析構(gòu)函數(shù)厌衔,那么查找到的析構(gòu)函數(shù)究竟是什么呢璧帝?

用代碼測(cè)試一下,先在類中增加一個(gè)屬性富寿,這樣就能進(jìn)入這個(gè)方法了:

// ZNObject.h
@interface ZNObject : NSObject
@property (nonatomic, copy) NSString *name;
@end

// ZNObject.m
@implementation ZNObject
- (void)dealloc {
    NSLog(@"znobject dealloc");
}

接著添加這些代碼用來(lái)測(cè)試dealloc的過程:

// ViewController.m
@interface ViewController() 
@property (nonatomic, strong) ZNObject *obj;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.obj = [ZNObject new];
}

- (void)viewDidAppear {
    self.obj = nil;
}

挺愚蠢的睬隶,其實(shí)加個(gè)大括號(hào)就可以了。

在self.obj = nil;這行先加個(gè)斷點(diǎn)页徐,進(jìn)入斷點(diǎn)之后苏潜,在object_cxxDestructFromClass()方法中添加一個(gè)斷點(diǎn),運(yùn)行進(jìn)入斷點(diǎn)如下圖:

尋找c++析構(gòu)函數(shù).png

先看左側(cè)的調(diào)用棧变勇,當(dāng)我將self.obj設(shè)為nil的時(shí)候恤左,就進(jìn)入了release的過程。這里引用計(jì)數(shù)為0搀绣,所以繼續(xù)進(jìn)入了dealloc的過程飞袋。先調(diào)用了ZNObject類中的dealloc,打印出了log豌熄,然后就進(jìn)入了本節(jié)dealloc的流程授嘀。

看看dtor的內(nèi)容,析構(gòu)函數(shù)是一個(gè)叫做.cxx_destruct的方法锣险,但是這個(gè)方法找不到實(shí)現(xiàn)的代碼蹄皱,怎么才能看到這個(gè)方法做了什么呢览闰?

換個(gè)思路試試看,c++的析構(gòu)函數(shù)是在對(duì)象有ivar的時(shí)候才會(huì)被調(diào)用巷折,調(diào)用的目的就是為了釋放這些ivar压鉴,那么我們只需要觀察ivar的變化情況就可以了。

調(diào)整一下測(cè)試的代碼锻拘,先給屬性賦個(gè)值油吭,再運(yùn)行:

觀察name屬性的變化.png

添加一個(gè)watchpoint,當(dāng)name變化的時(shí)候署拟,會(huì)自動(dòng)進(jìn)入斷點(diǎn)婉宰,直接運(yùn)行程序:

.cxx_destruct內(nèi)部.png

可以看到name的銷毀是在objc_storeStrong方法中進(jìn)行的,這個(gè)方法被.cxx_destroy調(diào)用推穷。遺憾的是心包,依然不能窺探到.cxx_destroy究竟做了些什么事。

講了這么多馒铃,其實(shí)就說(shuō)明了ivar釋放的過程蟹腾,下面進(jìn)入第二步。

part2
_object_remove_assocations(obj);

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

關(guān)于關(guān)聯(lián)對(duì)象区宇,最常見的應(yīng)用應(yīng)該就是在category中聲明屬性娃殖。這一塊這里就不展開了,后面談Associated Object的時(shí)候再說(shuō)吧议谷。

這里只要知道這一塊做的事情就是移除對(duì)象的associated object炉爆,更直接一點(diǎn)就是如果有這樣的代碼:

objc_setAssociatedObject(self.obj, @selector(obj), self.obj2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

在self.obj對(duì)象dealloc的時(shí)候,就會(huì)進(jìn)入_object_remove_assocations(obj)方法柿隙。

part3
obj->clearDeallocating();

inline void 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());
}

在這一節(jié)最開始提到了一共5個(gè)判斷條件叶洞,part1對(duì)應(yīng)的是4和part2對(duì)應(yīng)的是3,其他3個(gè)條件就都在這個(gè)方法里啦禀崖。

第一個(gè)if判斷對(duì)應(yīng)1衩辟,因?yàn)榛径际莕onpointer,所以直接看else中的內(nèi)容波附。else中對(duì)應(yīng)了2和5兩個(gè)條件艺晴,看看clearDeallocating_slow()是怎么做的:

void objc_object::clearDeallocating_slow()
{
    SideTable& table = SideTables()[this];
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
}

這里再次出現(xiàn)了SideTables,在上一篇講release的時(shí)候有提到過掸屡,SideTable存儲(chǔ)了溢出的引用計(jì)數(shù)封寞,也與弱引用相關(guān),這里其實(shí)就是對(duì)這兩部分進(jìn)行處理仅财。關(guān)于SideTable的具體內(nèi)容就不展開了狈究。這里只需要知道:

  • 如果有對(duì)此對(duì)象的弱引用,那么把所有的弱引用都置為nil盏求。weak對(duì)象設(shè)為nil原來(lái)就是在這里進(jìn)行的抖锥。
  • 如果引用計(jì)數(shù)曾經(jīng)溢出過亿眠,那么SideTable中就存儲(chǔ)過相關(guān)信息,當(dāng)然在這個(gè)時(shí)間點(diǎn)磅废,引用計(jì)數(shù)的值肯定是為0的纳像,但是即使是0也不放過,還要把曾經(jīng)存在的痕跡抹除掉拯勉。感覺好殘忍呀竟趾。。宫峦。

到這里就完成了dealloc的全部過程岔帽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市导绷,隨后出現(xiàn)的幾起案子山卦,更是在濱河造成了極大的恐慌,老刑警劉巖诵次,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異枚碗,居然都是意外死亡逾一,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門肮雨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)遵堵,“玉大人,你說(shuō)我怎么就攤上這事怨规∧八蓿” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵波丰,是天一觀的道長(zhǎng)壳坪。 經(jīng)常有香客問我,道長(zhǎng)掰烟,這世上最難降的妖魔是什么爽蝴? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮纫骑,結(jié)果婚禮上蝎亚,老公的妹妹穿的比我還像新娘。我一直安慰自己先馆,他們只是感情好发框,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煤墙,像睡著了一般梅惯。 火紅的嫁衣襯著肌膚如雪宪拥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天个唧,我揣著相機(jī)與錄音江解,去河邊找鬼。 笑死徙歼,一個(gè)胖子當(dāng)著我的面吹牛犁河,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魄梯,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼桨螺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了酿秸?” 一聲冷哼從身側(cè)響起灭翔,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辣苏,沒想到半個(gè)月后肝箱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稀蟋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年煌张,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片退客。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骏融,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萌狂,到底是詐尸還是另有隱情档玻,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布茫藏,位于F島的核電站误趴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏刷允。R本人自食惡果不足惜冤留,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望树灶。 院中可真熱鬧纤怒,春花似錦、人聲如沸天通。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至烘豹,卻和暖如春瓜贾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背携悯。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工祭芦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人憔鬼。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓龟劲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親轴或。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昌跌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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