內(nèi)存管理

本文將介紹痢甘,內(nèi)存分布咱士、內(nèi)存管理

一骑晶、內(nèi)存分布

內(nèi)存主要分為五大區(qū),按照地址從高向低依次為:棧區(qū) -> 堆區(qū) -> 全局區(qū) -> 常量區(qū) -> 代碼區(qū)(__text)

image.png

補(bǔ)
這里內(nèi)存指的是程序加載到cpu時(shí)的虛擬內(nèi)存
iOS應(yīng)用的虛擬內(nèi)存默認(rèn)分配4G大小铸敏,五大區(qū)占3G,還有1G是五大區(qū)之外的系統(tǒng)內(nèi)核區(qū)

每個(gè)區(qū)放置的內(nèi)容不一樣

  • 棧區(qū):函數(shù),方法,局部變量女气,對象指針肮之。由系統(tǒng)自動(dòng)管理(高地址像地址擴(kuò)展,是一塊連續(xù)的內(nèi)存區(qū)域)
  • 堆區(qū):通過alloc衣摩、malloc昂验、realloc開辟的對象,是不連續(xù)的內(nèi)存區(qū)域艾扮,以鏈表結(jié)構(gòu)存在既琴。手動(dòng)管理(目前ARC自動(dòng)管理);
  • 全局區(qū):全局變量泡嘴,靜態(tài)變量甫恩,空間由系統(tǒng)管理,static修飾的變量僅執(zhí)行一次酌予,生命周期為整個(gè)程序運(yùn)行期磺箕。
  • 常量區(qū):常量(整型奖慌、字符型,浮點(diǎn)松靡,字符串等))简僧。空間由系統(tǒng)管理雕欺,生命周期為整個(gè)程序運(yùn)行期岛马。
  • 代碼區(qū)(.text):存放代碼的區(qū)域,編譯完后屠列,是cpu可執(zhí)行的指令蛛枚。

補(bǔ):
1、全局區(qū)也叫靜態(tài)區(qū)分為DATA段和BSS段脸哀。DATA段(全局初始化區(qū))存放初始化的全局變量和靜態(tài)變量蹦浦;BSS段(全局未初始化區(qū))存放未初始化的全局變量和靜態(tài)變量。程序運(yùn)行結(jié)束時(shí)自動(dòng)釋放撞蜂。其中盲镶,BSS段在程序執(zhí)行之前會(huì)被系統(tǒng)自動(dòng)清零,所以未初始化全局變量和靜態(tài)變量在程序執(zhí)行之前已經(jīng)為0蝌诡。
2溉贿、在其他文件中定義的全局變量,在本文件中更改浦旱,只對本文件有效宇色。

通過一些例子可以測試不同區(qū)的地址不同
1、棧區(qū)和堆區(qū)


image.png

2颁湖、全局靜態(tài)區(qū)和常量區(qū)


image.png

可以看出宣蠕,
棧區(qū)是以0x7開頭的地址,
全局區(qū)常量區(qū)一般以0x1開頭的地址甥捺,
堆區(qū)0x6開頭的地址

二抢蚀、內(nèi)存管理方案

先介紹兩個(gè)概念:TaggedPointer和Nonpointer
nonpointer:其實(shí)指的是使用nonpointer-isa(非指針對象,對isa進(jìn)行地址優(yōu)化對象)镰禾,我們一般創(chuàng)建的對象都是這個(gè)nonpointer對象
Taggedpointer:小對象皿曲,短的string,NSNumber和NSDate對象
它的指針的值不再是地址吴侦,而是真正的值屋休。所以實(shí)際上它不再是一個(gè)對象,只是叫做對象备韧,其實(shí)只是一個(gè)普通變量劫樟。它的內(nèi)存并不存儲(chǔ)在堆中,所以不需要malloc和free。

下面我們用NSString的一個(gè)例子來說明這兩種類型

image.png

同樣的對self.nameStr賦值毅哗,執(zhí)行第二個(gè)听怕,頁面就崩潰了

image.png

且崩到了objc_release里捧挺,是因?yàn)檫@里存在過度釋放虑绵。我們可以看下這里的namestr類型

第一個(gè)

image.png

第二個(gè)
image.png

同一個(gè)對象,類型變了闽烙?是的翅睛。

  • 因?yàn)榈谝粋€(gè)里面,字符串比較短黑竞,所以系統(tǒng)會(huì)安排其為小對象(TaggedPointer)捕发,第二個(gè)字符串比較長,所以安排其為nonpointer對象很魂。
  • 所以第一個(gè)方法里扎酷,namestr不是一個(gè)真正的對象,只是一個(gè)常量遏匆,不需要set法挨、get方法,由系統(tǒng)負(fù)責(zé)管理內(nèi)存空間幅聘。
  • 而第二個(gè)方法中凡纳,namestr是一個(gè)對象,賦值時(shí)帝蒿,調(diào)用set方法(新值的retain荐糜,舊值的release),所以多線程操作時(shí)葛超,可能存在上一個(gè)舊值剛release完暴氏,其他線程又要release,導(dǎo)致過度釋放绣张,所以崩潰了偏序。
  • 那么多少長度的string就切換指針類型呢,如下


    image.png

2.1胖替、分析taggedpointer

之所以用小對象研儒,是因?yàn)椋ο笠?個(gè)字節(jié)独令,就是64位端朵,而有些值根本用不完64位,所以就用小對象(地址里就包含值)燃箭,可以節(jié)省內(nèi)存冲呢,提高性能。
先來打印幾個(gè)小對象看下內(nèi)存地址


image.png

以上的a招狸、b都是小對象~
但從打印結(jié)果看來敬拓,值和地址間也看不出有關(guān)聯(lián)關(guān)系邻薯。??當(dāng)然表面看不出來啦,因?yàn)閠aggedpointer在初始化時(shí)肯定要進(jìn)行混淆~

1乘凸、結(jié)構(gòu)
到objc源碼中看下taggedpointer的初始化厕诡,可以大致看出,進(jìn)行了混淆营勤。

image.png

  • 再去搜這個(gè)objc_debug_taggedpointer_obfuscator
    image.png
  • 看到了tagpointer指針地址的解碼和編碼方法灵嫌,用的是異或,那么兩次異或就會(huì)還原指針地址葛作。我們就在外面用一下解碼函數(shù)_objc_decodeTaggedPointer_寿羞,拿出string真正的指針地址
image.png
  • 結(jié)果顯示:61就是a的ASCII碼,62就是b的ASCCII碼赂蠢,那么taggedpinter指針包含了值绪穆!

再試一下number的地址

image.png

  • nstring和nsnumber的頭部不一樣(0xa和0xb),這又代表什么虱岂?
    猜測是為了表示是否是tagged指針玖院,去源碼中搜索


    image.png
  • 找到判斷函數(shù),其中
    # define _OBJC_TAG_MASK (1UL<<63)代表這個(gè)mask是最高位是1量瓜,那么上面那個(gè)isTaggedPointer函數(shù)里的算法意思就是司恳,只要最高位為1,那么它就是tagged指針類型

  • 0xa和0xb化為二進(jìn)制分別為(1010绍傲,1011)扔傅,最高位都是1,所以它們都是tagged類型烫饼。(此處也可以驗(yàn)證非tagged類型的值)

  • 最高位用來確定了tagged類型猎塞,那么后面10、11又用來代表什么呢杠纵?猜測是為了代表不同的類型(NSString和NSSNumber)
    找到判斷類型的函數(shù)


    image.png

點(diǎn)進(jìn) OBJC_TAG_Last60BitPayload這個(gè)判斷條件荠耽,

image.png

  • 果然是用來確定類型的,下面驗(yàn)證一下NSDate比藻,是否是這套邏輯铝量,
    image.png

    其中(e:1110)-> 最高位是1,說明是tagged指針银亲,后面三位是6慢叨,對照上面的enum,是TAG_NSDate!
由此务蝠,我們可以得到taggedPointer的結(jié)構(gòu)

1拍谐、指針地址
2、tagged類型的flag
3、值
4轩拨、是否是tagged

  • 這樣的一個(gè)類型践瓷,包含了這么多信息,而且是存在常量區(qū)亡蓉,由系統(tǒng)自動(dòng)管理晕翠,讀取的效率是相當(dāng)的。根據(jù)官方寸宵,是非taggedpointter的3倍崖面,創(chuàng)建的速度比非tagged106倍元咙。
    所以日常開發(fā)中梯影,給NSString、NSNumber庶香、NSDate賦值時(shí)甲棍,盡可能直接使用常量,有助于提高性能赶掖。

2.2感猛、分析nonponiter內(nèi)存管理

說到內(nèi)存管理,自然想到引用計(jì)數(shù)奢赂,和引用計(jì)數(shù)相關(guān)的陪白,就是這幾個(gè)操作:alloc,retain膳灶、release咱士、dealloc
使用最多的set方法就是包含了新值的retain,舊值的release轧钓,那么就從set方法開始
從源碼中可以得到set流程(此源碼可以自己查看):
objc_setProperty -> reallySetProperty -> objc_retain(newValue)->objc_release(oldValue)

2.2.1 objc_retain
源碼顯示序厉,objc_retain又調(diào)用了retain -> rootRetain

objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    //判斷是否是tag,如果是毕箍,直接返回
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;
//因?yàn)橐糜?jì)數(shù)存在isa里的extra_c里
    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //如果不是nonpointer弛房,直接操作散列表對引用計(jì)數(shù)操作+1
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        //如果正在釋放,清空散列表而柑,
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        //執(zhí)行引用計(jì)數(shù)+1操作文捶,即對bits中的 1ULL<<45(arm64) 即extra_rc,用于該對象存儲(chǔ)引用計(jì)數(shù)值
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
//判斷extra_rc是否滿了媒咳,carry是標(biāo)識(shí)符
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            // //如果extra_rc滿了粹排,則直接將滿狀態(tài)的一半拿出來存到extra_rc
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        //將另一半存在散列表的中,即滿狀態(tài)下是8位伟葫,RC_HALF=一半就是1左移7位恨搓,即除以2
        //這么操作的目的在于提高性能,因?yàn)槿绻即嬖谏⒘斜碇校?dāng)需要release時(shí)斧抱,引用計(jì)數(shù)-1常拓,都需要去訪問散列表,每次都需要開解鎖辉浦,比較消耗性能弄抬。extra_rc存儲(chǔ)一半的話,可以優(yōu)先直接操作extra_rc即可宪郊,不需要操作散列表掂恕。性能會(huì)提高很多
        sidetable_addExtraRC_nolock(RC_HALF);
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

以上主要分為幾個(gè)步驟

  • 判斷是否時(shí)taggedPointer,如果是弛槐,則直接返回自身懊亡,不操作任何
  • 判斷是否是Nonpointer_isa(do-while)
  • 引用計(jì)數(shù)操作
    1、如果不是nonponinter_isa,直接操作散列表SideTable乎串。進(jìn)行開鎖解鎖店枣。
    2、判斷是否正在釋放叹誉,如果是鸯两,調(diào)用dealloc,
    3长豁、如果不是钧唐,則對extra_c?1操作,并給一個(gè)引用計(jì)數(shù)的狀態(tài)標(biāo)識(shí)carry匠襟,用于表示extra_rc是否滿了
    4钝侠、如果extra_rc滿了,那么操作散列表宅此,將一半的引用計(jì)數(shù)存在散列表里机错。

下面查看釋放過程

2.2.2 objc_release
搜索objc_release,可以得到調(diào)用流程:objc_release ->release -> rootRelease
rootRelease源碼:

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //判斷是否是Nonpointer isa
        if (slowpath(!newisa.nonpointer)) {
            //如果不是,則直接操作散列表-1
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        //進(jìn)行引用計(jì)數(shù)-1操作父腕,即extra_rc-1
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        //如果此時(shí)extra_rc的值為0了弱匪,則走到underflow
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;
    //判斷散列表中是否存儲(chǔ)了一半的引用計(jì)數(shù)
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.
        //從散列表中取出存儲(chǔ)的一半引用計(jì)數(shù)
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            //進(jìn)行-1操作,然后存儲(chǔ)到extra_rc中
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }
    //此時(shí)extra_rc中值為0璧亮,散列表中也是空的萧诫,則直接進(jìn)行析構(gòu),即自動(dòng)觸發(fā)dealloc流程
    // Really deallocate.
    //觸發(fā)dealloc的時(shí)機(jī)
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        //發(fā)送一個(gè)dealloc消息
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

以上分為這幾個(gè)步驟

  • 判斷是否是taggedPointer枝嘶,若是帘饶,返回no,不做任何操作群扶,
  • 判斷時(shí)否是nonPointer及刻,如果不是镀裤,直接操作散列表side table,引用計(jì)數(shù)-1,
  • 如果是nonPointer缴饭,則
    1暑劝、對extra_rc -1,并存儲(chǔ)當(dāng)前extra_rc的狀態(tài)為carry(一直減1,直到extrc_rc==0時(shí)跳到下一步)
    2颗搂、extrc_rc==0担猛,跳到underflow
    underflow
    3、判斷散列表中是否存儲(chǔ)了一半的引用計(jì)數(shù)丢氢,如果是的傅联,則從散列表中取出存儲(chǔ)的一半引用計(jì)數(shù),-1操作疚察,存儲(chǔ)到extra_rc
    4蒸走、如果散列表中為,而此時(shí)extra_rc也為稍浆,則直接進(jìn)行析構(gòu)载碌,即自動(dòng)觸發(fā)dealloc操作

從retain和release操作猜嘱,可以發(fā)現(xiàn)這是兩個(gè)相反的操作流程衅枫,那么其中的散列表sideTable具體是啥呢?
繼續(xù)往下分析

2.3朗伶、散列表sideTable

從2.2中弦撩,知曉sideTable的作用是用于
一個(gè)是非nonpointer_isa對象的引用計(jì)數(shù)使用
另外一個(gè)重要的作用是 nonpointer時(shí),當(dāng)引用計(jì)數(shù)值過大時(shí)论皆,會(huì)將一半引用計(jì)數(shù)存到它里面
我們先去看下stable的結(jié)構(gòu)

struct SideTable {
    spinlock_t slock;//開/解鎖
    RefcountMap refcnts;//引用計(jì)數(shù)表
    weak_table_t weak_table;//弱引用表
    
    ....
}

從類型看出益楼,它是一個(gè)結(jié)構(gòu)體,包含了点晴、引用計(jì)數(shù)表感凤、弱引用表,所以上面的引用計(jì)數(shù)都存到它其中的引用計(jì)數(shù)表中了粒督。
那么它就是一張表么陪竿,還是多個(gè)?

  • 查看sidetable_unlock方法屠橄,定位到SideTables,
objc_object::sidetable_unlock()
{
    SideTable& table = SideTables()[this];
    table.unlock();
}
  • 看出SideTables其實(shí)是一個(gè)數(shù)組族跛,在操作開|解鎖時(shí),其實(shí)只是操作其中一張表

再看一下SideTables的獲取

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;

static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}
  • 是由 StripedMap通過get方法獲取

再看一下StripedMap結(jié)構(gòu)

image.png

  • 內(nèi)存中最多只有8張散列表(真機(jī))锐墙,64張(非真機(jī))礁哄,并且重構(gòu)了[ ]操作符,直接通過對象內(nèi)存地址通過indexForPointer得到下標(biāo)溪北,再使用[ ]獲取到對應(yīng)的sidetable

2.3.1 為什么只有8張表(真機(jī))

  • 如果每個(gè)對象都對應(yīng)一張散列表桐绒,首先那占用內(nèi)存很多夺脾,第二,每次操作引用計(jì)數(shù)時(shí)都要開/解鎖茉继,對整個(gè)程序性能不好
  • 如果整個(gè)內(nèi)存只有一張散列表共用劳翰,那么每個(gè)對象操作時(shí),都要開/解鎖馒疹,會(huì)暴露所有對象的引用計(jì)數(shù)佳簸、弱引用等信息,不安全~

2.3.2颖变、散列表是屬于哪種表結(jié)構(gòu)

  • 散列表是一種哈希表生均,key是關(guān)聯(lián)對象內(nèi)存地址的。哈希表的特點(diǎn)就是:查詢快腥刹、增刪改方便·马胧,整體性能好。(比如于tls衔峰,存儲(chǔ)結(jié)構(gòu)就是拉鏈形式的)
  • 而沒有使用鏈表和數(shù)組佩脊,因?yàn)?code>鏈表特點(diǎn)是:找到節(jié)點(diǎn)增刪改方便,但查詢慢(需要從頭節(jié)點(diǎn)開始遍歷查詢)垫卤,它屬于存儲(chǔ)快威彰,讀取慢。而數(shù)組特點(diǎn)是:查詢方便(即通過下標(biāo)訪問)穴肘,增刪改比較麻煩歇盼,它屬于讀取快存儲(chǔ)改不方便评抚。

2.3.3豹缀、上面retain過程為什么只存儲(chǔ)一半引用計(jì)數(shù)到表里

  • 為了提高性能
    extra_rc的引用計(jì)數(shù)滿了,就需要操作散列表慨代,將滿狀態(tài)的半拿出來extra_rc邢笙,另一半散列表中。是因?yàn)槿绻?code>都存儲(chǔ)在散列表侍匙,每次對散列表操作都需要開/解鎖氮惯,操作耗時(shí)消耗性能大丈积,這么一半分操作目的就是提高性能

*以上是散列表的補(bǔ)充筐骇,那么還有一個(gè)重要的函數(shù)dealloc

2.3、dealloc分析

搜索源碼中dealloc
得到調(diào)用順序:dealloc -> _objc_rootDealloc -> object_dispose
rootDealloc源碼:

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    //沒有弱引用表江滨、關(guān)聯(lián)對象铛纬、c++函數(shù)、引用計(jì)數(shù)表唬滑,直接free
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        //如果有任何一個(gè)告唆,調(diào)用dispose
        object_dispose((id)this);
    }
}

object_dispose源碼:

object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

再跳入objc_destructInstance源碼:

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.
         //C++調(diào)用析構(gòu)函數(shù)棺弊、刪除關(guān)聯(lián)對象引用、
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        //如果不是nonpoint擒悬,則直接釋放散列表
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
       // 如果是nonponter模她,清空弱引用表 + 散列表
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

dealloc步驟

  • 調(diào)用c++析構(gòu)函數(shù)
  • 刪除關(guān)聯(lián)對象引用
  • 釋放引用計(jì)數(shù)表
  • 清空弱引用表
  • free釋放自己
    至此,整個(gè)retain-release流程分析完畢懂牧,下面歸納一下流程
    后補(bǔ)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侈净,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子僧凤,更是在濱河造成了極大的恐慌畜侦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躯保,死亡現(xiàn)場離奇詭異旋膳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)途事,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門验懊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尸变,你說我怎么就攤上這事义图。” “怎么了振惰?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵歌溉,是天一觀的道長。 經(jīng)常有香客問我骑晶,道長,這世上最難降的妖魔是什么草慧? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任桶蛔,我火速辦了婚禮,結(jié)果婚禮上漫谷,老公的妹妹穿的比我還像新娘仔雷。我一直安慰自己,他們只是感情好舔示,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布碟婆。 她就那樣靜靜地躺著,像睡著了一般惕稻。 火紅的嫁衣襯著肌膚如雪竖共。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天俺祠,我揣著相機(jī)與錄音公给,去河邊找鬼借帘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛淌铐,可吹牛的內(nèi)容都是我干的肺然。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼腿准,長吁一口氣:“原來是場噩夢啊……” “哼际起!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吐葱,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤加叁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后唇撬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體它匕,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年窖认,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豫柬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扑浸,死狀恐怖烧给,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喝噪,我是刑警寧澤础嫡,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站酝惧,受9級特大地震影響榴鼎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晚唇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一巫财、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哩陕,春花似錦平项、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至心赶,卻和暖如春扣讼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背园担。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工届谈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枯夜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓艰山,卻偏偏與公主長得像湖雹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子曙搬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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