iOS內(nèi)存管理-引用計數(shù)及autorelease

學(xué)習(xí)了好久的iOS內(nèi)存管理,一直是斷斷續(xù)續(xù)的,現(xiàn)在有時間找了個機會總結(jié)了一下,有時候時間久了好多知識點就會遺忘,希望能將這些點記下來,多看幾次。

前言:虛擬內(nèi)存

移動設(shè)備的內(nèi)存資源是有限的,當App運行時占用的內(nèi)存大小超過了限制后,就會被強殺掉,從而導(dǎo)致用戶體驗被降低。所以,為了提升App質(zhì)量,開發(fā)者要非常重視應(yīng)用的內(nèi)存管理問題。

移動端的內(nèi)存管理技術(shù),主要有兩種:

  • GC(Garbage Collection,垃圾回收)的標記清除算法;

  • Apple使用的引用計數(shù)方法。

相比較于 GC 標記清除算法,引用計數(shù)法可以及時地回收引用計數(shù)為0的對象,減少查找次數(shù)。但是,引用 計數(shù)會帶來循環(huán)引用的問題,比如當外部的變量強引用 Block時,Block 也會強引用外部的變量,就會出現(xiàn) 循環(huán)引用。我們需要通過弱引用,來解除循環(huán)引用的問題。

另外,在 ARC(自動引用計數(shù))之前,一直都是通過 MRC(手動引用計數(shù))這種手寫大量內(nèi)存管理代碼的 方式來管理內(nèi)存,因此蘋果公司開發(fā)了 ARC 技術(shù),由編譯器來完成這部分代碼管理工作。但是,ARC依然 需要注意循環(huán)引用的問題。

內(nèi)存管理的演進過程:在最開始的時候,程序是直接訪問物理內(nèi)存,但后來有了多程序多任務(wù)同時運 行,就出現(xiàn)了很多問題。比如,同時運行的程序占用的總內(nèi)存必須要小于實際物理內(nèi)存大小。再比如,程序 能夠直接訪問和修改物理內(nèi)存,也就能夠直接訪問和修改其他程序所使用的物理內(nèi)存,程序運行時的安全就 無法保障。

虛擬內(nèi)存:

由于要解決多程序多任務(wù)同時運行的這些問題,所以增加了一個 中間層 來間接訪問物理內(nèi)存,這個中間層就是虛擬內(nèi)存。虛擬內(nèi)存通過映射,可以將虛擬地址轉(zhuǎn)化成物理地址。

虛擬內(nèi)存會給每個程序創(chuàng)建一個單獨的執(zhí)行環(huán)境,也就是一個獨立的虛擬空間,這樣每個程序就只能訪問自 己的地址空間(Address Space),程序與程序間也就能被安全地隔離開了。

32位的地址空間是 2^32 = 4294967296 個字節(jié),共 4GB,如果內(nèi)存沒有達到 4GB 時,虛擬內(nèi)存比實際的物 理內(nèi)存要大,這會讓程序感覺自己能夠支配更多的內(nèi)存。如同虛擬內(nèi)存只供當前程序使用,操作起來和物理 內(nèi)存一樣高效。

有了虛擬內(nèi)存這樣一個中間層,極大地節(jié)省了物理內(nèi)存。iOS的共享庫就是利用了這一點,只占用一份物理 內(nèi)存,卻能夠在不同應(yīng)用的多份虛擬內(nèi)存中,去使用同一份共享庫的物理內(nèi)存。

1、 引用計數(shù) RetainCount

在iOS開發(fā)中,使用 引用計數(shù) 來管理OC對象的內(nèi)存:

  • 一個新創(chuàng)建的OC對象,它的引用計數(shù)是1,當引用計數(shù)減為0 ,OC對象就會被銷毀,釋放其占用的內(nèi)存空間;
  • 調(diào)用retain會讓對象的引用計數(shù)+1;調(diào)用release會讓對象的引用計數(shù)-1;
  • 內(nèi)存管理總結(jié)經(jīng)驗:
    • 當調(diào)用alloc,new,copy,mutablecopy等返回一個對象,在不需要這個對象的時候,需要對這個對象進行release或者autorelease操作;
    • 想擁有某個對象,就讓它的引用計數(shù)+1;不想擁有某個對象,就讓它的引用計數(shù)-1;

1.1 引用計數(shù)存放的位置

從64bit開始,對象的引用計數(shù)存放在優(yōu)化過的isa指針中,也可能存放在sideTable中:

isa結(jié)構(gòu)體詳情.png
  • extra_rc:存放的是對象的引用計數(shù)值減1;
  • has_sidetable_rc: 如果引用計數(shù)值過大,extra_rc中存放不下,這時候此值為1,對象的引用計數(shù)存放在sidetable中;
isa中存放的值.png

1.2 引用計數(shù)存放的位置sidetable和retainCount、release

1.2.1 SideTables與SideTable

當優(yōu)化過的isa指針中,引用計數(shù)過大存放不下時,就會將引用計數(shù)存放到SideTable中;

SideTables其實是一個哈希表,可以通過對象的指針找到對象內(nèi)容具體存放在哪個SideTable中:

SideTables

通過指針找到對象引用計數(shù)存放的SideTable:

image.png

SideTable對應(yīng)結(jié)構(gòu)如下圖:

SideTable結(jié)構(gòu)

在runtime源碼中,NSObje.mm可以看到SideTable結(jié)構(gòu)體源碼:

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts; //referanceCount:引用計數(shù)表
    weak_table_t weak_table;//弱引用表:存放的對象的弱引用指針
};

問題:為什么不是一個SideTable而是多個SideTable?或者(為什么不將所有的對象放到一個table里面,而是放到不同的side-table里面?)

查找或者修改引用計數(shù)的時候是要加鎖的,如果有多個對象同時查找引用計數(shù):

  • 只有一張表的話,查詢肯定是需要加鎖,同步有先后順序的;
  • 如果是有多張表,就可以異步進行查詢,不同的表之間查詢是沒有影響的;--> 效率更高

1.2.2 retainCount

SideTable中的引用計數(shù)表中RefcountMap,存放的就是對象的引用計數(shù):retainCount
runtime源碼如下:


_objc_rootRetainCount(id obj){
    return obj->rootRetainCount();
}

objc_object::rootRetainCount()
{
    //如果是taggerPointer,直接返回指針;
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    
    //判斷是否是優(yōu)化過的isa指針
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;//extra_rc+1 isa指針中存儲的引用計數(shù)值;
        if (bits.has_sidetable_rc) {
            // has_sidetable_rc值如果為1:表示引用計數(shù)存放在sidetable中
            rc += sidetable_getExtraRC_nolock();//細節(jié):注意+=
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    
    //不是優(yōu)化過的isa指針直接查找SideTable
    return sidetable_retainCount();
}

//優(yōu)化過的isa指針在SideTable中查找引用計數(shù)
size_t objc_object::sidetable_getExtraRC_nolock()
{
    ASSERT(isa.nonpointer);
    
    //找到對應(yīng)的table
    SideTable& table = SideTables()[this];
    
    //table.refcnts(引用計數(shù)表)中找到對應(yīng)計數(shù)it
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

//沒有優(yōu)化過的isa指針在SideTable中查找引用計數(shù)
objc_object::sidetable_retainCount()
{
    //從sideTables中找出當前對象的sideTable
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    
    //table.refcnts(引用計數(shù)表)中找到對應(yīng)計數(shù)it
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}


步驟:

  • 判斷是否是TaggerPointer,如果是,直接返回指針
  • 判斷是否是優(yōu)化過的isa指針:
    • 如果是優(yōu)化過的isa指針,先讀取isa指針中存放的引用計數(shù)extra_rc
      • 如果has_sidetable_rc為1,代表引用計數(shù)存放在SideTable中;
      • 注意:rc = 1 + bits.extra_rc;-->rc += sidetable_getExtraRC_nolock();二者之和;
    • 如果不是優(yōu)化過的isa指針,直接去SideTable中去查找引用計數(shù)值;

1.2.3 release

當調(diào)用alloc,new,copy,mutablecopy等返回一個對象,在不需要這個對象的時候,需要對這個對象進行release或者autorelease操作,讓對象的引用計數(shù)-1;

  • 在ARC中,LLVM編譯器會自動幫我們生成對應(yīng)的[xxx release];
  • 在MRC中,需要我們手動添加[xxx release];

當對象的引用計數(shù)減為0時,就會調(diào)用dealloc()函數(shù);

- (oneway void)release {
    _objc_rootRelease(self);
}

_objc_rootRelease(id obj){
    obj->rootRelease();
}

objc_object::rootRelease(bool performDealloc, bool handleUnderflow){
    if (isTaggedPointer()) return false;
    
    if (slowpath(!newisa.nonpointer)) {
        return sidetable_release(performDealloc);
    }
   
    //has_sidetable_rc為1,引用計數(shù)存放在sidetable中
    if (slowpath(newisa.has_sidetable_rc)) {
     
        // 從sidetable中讀取對象引用計數(shù)值        
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
        if (borrowed > 0) {
            newisa.extra_rc = borrowed - 1;  
            // 存儲修改過的引用計數(shù)值
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            return false;
        }
    }

    // Really deallocate.
    //如果對象的引用計數(shù)減為0,使用objc_msgSend()發(fā)送消息調(diào)用dealloc()方法;
    if (performDealloc) {
        objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

  • 注意在引用計數(shù)減為0時,會發(fā)送消息調(diào)用對象的dealloc()方法銷毀對象,并釋放對象占用的內(nèi)存空間;

1.3 weak指針實現(xiàn)原理

示例如下圖:
YYPerson只是重寫了dealloc方法,添加打印對象釋放時機;

  • 當在作用域{}中,LLVM編譯器會自動添加release操作,使得引用計數(shù)-1;

    • __strong YYPerson * person1; // 強指針
    • __weak YYPerson * person2; // 弱指針
    • __unsafe_unretained YYPerson * person3; // 弱指針

1、當沒有任何指針指向[YYPerson alloc]創(chuàng)建出來的對象時,出了{}作用域,直接釋放:

image.png

2、 強引用:__strong:person1
當有強引用指針指向[YYPerson alloc]創(chuàng)建出來的對象時,引用計數(shù)+1;

image.png

3、弱引用

  • __weak:person2
  • __unsafe_unretained:person3

當有弱引用指針指向[YYPerson alloc]創(chuàng)建出來的對象時,引用計數(shù)不變;現(xiàn)象如下:

image.png

1.3.1 __weak 和 __unsafe-unretained區(qū)別

在對象釋放以后調(diào)用weak指針: 打印顯示指針為空;

weak打印.png

在對象釋放以后調(diào)用:_unsafe-unretained指針,直接崩潰,顯示野指針錯誤(指針指向地址的對象已經(jīng)被釋放)

__unsafe_unretained打印.png

區(qū)別

  • weak指針在對象銷毀時,dealloc()方法會調(diào)用rootDealloc方法,最終會判斷,如果該對象有弱引用,會將存放弱引用的散列表weakTable清空,所以最終weak指針會置為nil;

  • __unsafe_unretained不會有將對應(yīng)指針清空的操作,所以不太安全,在對象銷毀釋放以后,再去調(diào)用指針就回造成壞內(nèi)存訪問:EXC-BAD-ACCESS;

  • 注意: weak指針置為nil后,再調(diào)用方法,在objc_msgSend()中,會先判斷,如果消息接收者為空,則直接return,所以不會調(diào)用方法,也不會crash;

1.3.2 objc_initWeak()

在進行編譯過程前,clang 其實對 __weak 做了轉(zhuǎn)換,調(diào)用objc_initWeak,將對應(yīng)的弱引用指針存儲到SideTable中:

NSObject objc_initWeak(&p, 對象指針);

id objc_initWeak(id *location, id newObj) {
    // 查看對象實例是否有效
    // 無效對象直接導(dǎo)致指針釋放
    if (!newObj) {
        *location = nil;
        return nil;
    }

    // 這里傳遞了三個 bool 數(shù)值
    // 使用 template 進行常量參數(shù)傳遞是為了優(yōu)化性能
    // storeWeak:將弱引用指針存放到SideTable中
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

這一塊知識點參考了瓜神詳細講解weak實現(xiàn)原理的博文:
瓜神-弱引用的實現(xiàn)方式

1.4 dealloc()方法

當一個對象的引用計數(shù)減為0時,就會調(diào)用dealloc()方法釋放對象并清理對應(yīng)的內(nèi)存空間

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

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

objc_object::rootDealloc(){
    if (isTaggedPointer()) return;  // 如果是isTaggedPointer直接return

    /*
      isa.nonpointer :0,代表普通的指針,存儲著Class、Meta-Class對象的內(nèi)存地址
                       1,代表優(yōu)化過,使用位域存儲更多的信息
    
      isa.weakly_referenced:是否有被弱引用指向過,如果沒有,釋放時會更快

      
      isa.has_assoc:是否有設(shè)置過關(guān)聯(lián)對象,如果沒有,釋放時會更快
      
    
      isa.has_sidetable_rc:引用計數(shù)器是否過大無法存儲在isa中,如果為1,那么引用計數(shù)會存儲在一個叫SideTable的類的屬性中
    **/

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        free(this);//上述條件都不滿足,直接free(),釋放當前對象
    } else {
        object_dispose((id)this);//清理其他相關(guān)和引用
    }
}


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

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // 判斷是否有c++析構(gòu)函數(shù)和關(guān)聯(lián)對象
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // 處理c++析構(gòu)函數(shù)相關(guān):清除成員變量
        if (cxx) object_cxxDestruct(obj);
        //移除關(guān)聯(lián)對象
        if (assoc) _object_remove_assocations(obj);
        //將指向當前對象的弱引用指針置為nil
        obj->clearDeallocating();
    }

    return obj;
}

objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // 普通的isa指針
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // 優(yōu)化過的isa指針
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

objc_object::clearDeallocating_slow(){
    
    //從 SideTables中取出指針對應(yīng)存放的SideTable
    SideTable& table = SideTables()[this];
    
    //如果有弱引用,清除弱引用指針
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    
    //如果引用計數(shù)不是存放在isa中,而是存放在SideTable中,清除SideTable中refcnts(引用計數(shù)表)中的引用計數(shù)
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
}

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {
    objc_object *referent = (objc_object *)referent_id;

    //通過弱引用表weak_table和弱引用指針referent,找出weak_table中弱引用對象的索引entry
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        return;
    }

    // zero out references
    
    //DisguisedPtr<objc_object *> weak_referrer_t; 
    //weak_referrer_t是一個集合類型

    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            //遍歷如果referrer == 傳進來的referent,置為nil
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    //清空weak_table對應(yīng)索引entry中的內(nèi)容
    weak_entry_remove(weak_table, entry);
}


2. autorelease 和 AutoReleasePool

在MRC環(huán)境下,有些對象的釋放調(diào)用了[XXX autoerlease]來釋放對象,?調(diào)用autorelease的對象不會立即釋放,也就是對象的引用計數(shù)不會立馬 -1;

通過對autorelease方法的研究發(fā)現(xiàn):

調(diào)用autorelease 的對象的生命周期是通過一個叫AutoreleasePoolPage的對象來管理的,調(diào)用autorelease的對象,其實在@autoreleasepool中執(zhí)行了一下操作:

@autoreleasepool {
//        atautoreleasepoolobj = objc_autoreleasePoolPush();
       執(zhí)行操作        
//        objc_autoreleasePoolPop(atautoreleasepoolobj);
}

  • objc_autoreleasePoolPush(): 入棧 --> 將對象加入到AutoreleasePoolPage表中;

  • objc_autoreleasePoolPop(): 出棧 --> 將對象從AutoreleasePoolPage表中移除;

2.1 AutoReleasePoolPage結(jié)構(gòu)

調(diào)用autoreleasePoolPush()函數(shù)操作時,會將調(diào)用autorelease的對象加入到AutoReleasePoolPage中;

AutoReleasePoolPage其實是:以棧為節(jié)點通過雙向鏈表的形式鏈接起來的數(shù)據(jù)結(jié)構(gòu)

AutoReleasePoolPage結(jié)構(gòu)體成員

其結(jié)構(gòu)體成員中,有以下注意的地方

  • next : 指向的是下一個可以存放元素的位置;
  • thread : 線程,AutoReleasePoolPage和線程是對應(yīng)關(guān)系
  • parent : 雙向鏈表中的 prev指針,指向上一個AutoReleasePoolPage
  • child : 雙向鏈表中的 next指針,指向下一個AutoReleasePoolPage
AutoReleasePoolPage結(jié)構(gòu)

2.1.1 autoreleasePoolPush

AutoReleasePoolPage是一個 棧的結(jié)構(gòu),棧的特點是: 先進后出 :

如下圖,入棧順序為0-9,出棧順序為9-0:

棧:先進后出

在執(zhí)行push操作時,例如添加一個obj(3):

  • 先將哨兵對象指向的位置置為nil;

  • 將push進來的對象指針添加到

  • 再將next指針和哨兵對象向上移動;

如下圖:哨兵對象其實是指一個值為POOL_BOUNDARY的值,這個標識了當前autoreleasePool池的起始位置;

push

autorelease流程:

首先判斷next指針是否在棧頂,每個autorelpool都是4096字節(jié):

  • 當next指針指向棧頂?shù)臅r候,當前page已存滿,重新創(chuàng)建一個page對象來存放push進來的對象
  • 當next不是棧頂時,直接執(zhí)行入棧操作;
image.png

2.1.2 autoreleasePoolPop

執(zhí)行pop操作有以下流程:

  • 根絕傳入的哨兵對象找到對應(yīng)的位置
  • 對上次執(zhí)行push操作添加的所有對象依次發(fā)送release消息
  • 回退next指針到正確的位置(到另一個哨兵對象的位置,一個page對象只有一個next指針,但是哨兵對象可以有多個)

執(zhí)行pop操作前:


執(zhí)行pop操作

執(zhí)行pop操作結(jié)束后:到下一個哨兵對象前的對象都被釋放,并改變next指針的位置。

執(zhí)行pop操作結(jié)束

所以,總結(jié)一下:

  • 調(diào)用push方法會將一個POOL_BOUNDARY(哨兵對象)入棧,并且返回其存放的內(nèi)存地址

  • 調(diào)用pop方法時傳入一個POOL_BOUNDARY(哨兵對象)的內(nèi)存地址,會從最后一個入棧的對象開始發(fā)送release消息,直到遇到這個POOL_BOUNDARY

  • id *next指向了下一個能存放autorelease對象地址的區(qū)域

2.1.2 多個@autoreleasepool和嵌套使用

在有多個@autoreleasepool{}時,遵循3步:
1、 push入棧;
2、 執(zhí)行代碼;
3、 pop出棧;

多個平級的@autoreleasepool{};

image.png

多個嵌套的@autoreleasepool{};


image.png

3. autorelease與runloop的關(guān)系

經(jīng)過前面的了解,調(diào)用autorelease的對象都是通過autoreleasePoolPage來管理的,而autoreleasePoolPage結(jié)構(gòu)體對象中,有一個NSThread對象,說明autoreleasePoolPage來管理對象的入棧和出棧和線程有一定的關(guān)系,而線程處理任務(wù)離不開runloop,所以得仔細探究一下runLoop和autorelease之間的聯(lián)系;

我們知道,在Runloop中,runloop有不同的模式,每種Mode下都會有observers,source0,source1,timers,_name,其中sources和timers是runloop需要處理的任務(wù);

處理timer

在runloop每一次運行循環(huán)中,都會處理一遍所有的timers和blocks,然后進入休眠狀態(tài),如果有事件處理,就被喚醒,再次進行循環(huán),處理一遍這些事情;

但是在runloop進行休眠之前,也就是在狀態(tài)kCFRunLoopBeforeWaiting之前,會處理一些特殊的事情,比如刷新界面的UI:

問題: 在修改UI,背景色或添加一個subview等操作后,是立即生效執(zhí)行的嗎?

答案是:不會立即生效,而是在當前線程進入休眠,也就是kCFRunLoopBeforeWaiting之前進行刷新操作(刷新UI是在主線程,所以當前線程是指主線程);

所以猜測: 調(diào)用autorelease的對象也有可能是在當前線程休眠的時候出棧釋放的;

以下進行驗證:

3.1 添加observer監(jiān)聽runloop的狀態(tài)

runloop的狀態(tài)是一個枚舉,其對應(yīng)的各種狀態(tài)值如下:

runloop狀態(tài)

有以下兩種方式添加一個observer監(jiān)聽Runloop的狀態(tài):(注意C語言創(chuàng)建的對象最后都需要release)

方式一: 添加一個C語言O(shè)bserverCallBack()函數(shù)回調(diào):

void ObserverCallBack (CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
        break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
        break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
        break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
        break;
        
        break;
        default:
            break;
    }
}

- (void)observeRunloopMode{
    CFRunLoopObserverRef observeRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0,  ObserverCallBack, NULL);
    
    CFRunLoopRef current = CFRunLoopGetCurrent();
    
    CFRunLoopAddObserver( current, observeRef, kCFRunLoopCommonModes);
    
    CFRelease(current);

    CFRelease(observeRef);

}

方式二: 直接使用block(這種方式更簡單)

- (void)observeRunloopMode{

    CFRunLoopObserverRef observeRef =CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        switch (activity) {
            case kCFRunLoopEntry:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry ----- %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopBeforeWaiting:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopBeforeWaiting ----- %@-----------",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopAfterWaiting:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopAfterWaiting ----- %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopBeforeTimers:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopBeforeTimers ----- %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopBeforeSources:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopBeforeSources ----- %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit ----- %@",mode);
                CFRelease(mode);
                break;
                break;
            }
            default:
                break;
        }        
    });
    
    CFRunLoopRef current = CFRunLoopGetCurrent();
    
    CFRunLoopAddObserver( current, observeRef, kCFRunLoopCommonModes);
    
    CFRelease(current);

    CFRelease(observeRef);
}

有了監(jiān)聽Runloop狀態(tài)的方法,驗證猜測結(jié)果如下:

釋放時機

所以結(jié)論如下:

主線程 的Runloop中注冊了2個Observer :

  • 第1個Observer監(jiān)聽了kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush();(優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前)

  • 第2個Observer

    • 監(jiān)聽了kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush();

    • 監(jiān)聽了kCFRunLoopBeforeExit事件,會調(diào)用objc_autoreleasePoolPop()

子線程 中:

  • 子線程創(chuàng)建的時候就會創(chuàng)建一個autoreleasepool,并且在線程退出的時候,清空autoreleasepool。

結(jié)語

筆記中提到的內(nèi)容大部分總結(jié)自小碼哥底層原理,仔細總結(jié)起來發(fā)現(xiàn)好多細節(jié)要自己實現(xiàn)了才能深刻的理解;
學(xué)無止境,關(guān)于內(nèi)存管理這塊還有很多需要查缺補漏的地方,這篇只是自己的學(xué)習(xí)筆記,有不對的地方請見諒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容