iOS-底層原理 :內(nèi)存管理(二)AutoReleasePool 、RunLoop

本文主要分析 AutoReleasePool 以及 NSRunLoop 的底層實(shí)現(xiàn)

AutoReleasePool 自動(dòng)釋放池

自動(dòng)釋放池是OC中的一種內(nèi)存自動(dòng)回收機(jī)制,它可以將加入AutoreleasePool中的變量release的時(shí)機(jī)延遲东囚,簡單來說够吩,就是當(dāng)創(chuàng)建一個(gè)對(duì)象,在正常情況下囤攀,變量會(huì)在超出其作用域的時(shí)立即release软免。如果將對(duì)象加入到了自動(dòng)釋放池中,這個(gè)對(duì)象并不會(huì)立即釋放焚挠,會(huì)等到runloop休眠/超出autoreleasepool作用域{}之后才會(huì)被釋放膏萧。其機(jī)制如下圖所示

image
  • 1、從程序啟動(dòng)到加載完成蝌衔,主線程對(duì)應(yīng)的runloop會(huì)處于休眠狀態(tài)榛泛,等待用戶交互來喚醒runloop

  • 2、用戶的每一次交互都會(huì)啟動(dòng)一次runloop噩斟,用于處理用戶的所有點(diǎn)擊曹锨、觸摸事件等

  • 3、runloop在監(jiān)聽到交互事件后剃允,就會(huì)創(chuàng)建自動(dòng)釋放池艘希,并將所有延遲釋放的對(duì)象添加到自動(dòng)釋放池中

  • 4硼身、在一次完整的runloop結(jié)束之前,會(huì)向自動(dòng)釋放池中所有對(duì)象發(fā)送release消息覆享,然后銷毀自動(dòng)釋放池

Clang分析

根據(jù)之前源碼的分析經(jīng)驗(yàn)佳遂,我們先通過clang來分析

  • 定義如下代碼
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
}

  • 通過clang編譯成底層實(shí)現(xiàn),命令為:xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m
struct __AtAutoreleasePool {
    //構(gòu)造函數(shù)
    __AtAutoreleasePool() {
            atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    //析構(gòu)函數(shù)
    ~__AtAutoreleasePool() {
            objc_autoreleasePoolPop(atautoreleasepoolobj);
     }
      void * atautoreleasepoolobj;
};

int main(int argc, const char * argv[]) {
   { 
        //是一個(gè)結(jié)構(gòu)體
         __AtAutoreleasePool __autoreleasepool; 
    }
    return 0;
}

簡單來說撒顿,自動(dòng)釋放池其本質(zhì)也是一個(gè)對(duì)象

@autoreleasepool {}
//等價(jià)于
{__AtAutoreleasePool __autoreleasepool; }

  • __AtAutoreleasePool是一個(gè)結(jié)構(gòu)體丑罪,有構(gòu)造函數(shù) + 析構(gòu)函數(shù),結(jié)構(gòu)體定義的對(duì)象在作用域結(jié)束后凤壁,會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)

  • 其中{} 是 作用域 吩屹,優(yōu)點(diǎn)是結(jié)構(gòu)清晰,可讀性強(qiáng)拧抖,可以及時(shí)創(chuàng)建銷毀

關(guān)于涉及的構(gòu)造和析構(gòu)函數(shù)的調(diào)用時(shí)機(jī)煤搜,可以通過下面一個(gè)案例來驗(yàn)證

struct CJLTest{
    CJLTest
(){
        printf("1123 - %s\n", __func__);
    }
    ~CJLTest(){
        printf("5667 - %s\n", __func__);
    }
};

int main(int argc, const char * argv[]) {
    {
        CJLTest test;
    }
}

//**********運(yùn)行結(jié)果**********
1123 - CJLTest
5667 - ~CJLTest

從而可以得出,在CJLTest創(chuàng)建對(duì)象時(shí)唧席,會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù)擦盾,在出了{(lán)}作用域后,會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)

匯編分析

  • 在main代碼部分加斷點(diǎn)淌哟,運(yùn)行程序迹卢,并開啟匯編調(diào)試

    image

    通過調(diào)試結(jié)果發(fā)現(xiàn),證明了我們clang分析的結(jié)果

總結(jié)

  • autoreleasepool其本質(zhì)是一個(gè)結(jié)構(gòu)體對(duì)象徒仓,一個(gè)自動(dòng)釋放池對(duì)象就是頁腐碱,是是棧結(jié)構(gòu)存儲(chǔ),符合先進(jìn)后出的原則即可

  • 頁的棧底是一個(gè)56字節(jié)大小的空占位符掉弛,一頁總大小為4096字節(jié)

  • 只有第一頁哨兵對(duì)象症见,最多存儲(chǔ)504個(gè)對(duì)象,從第二頁開始最多存儲(chǔ)505個(gè)對(duì)象

  • autoreleasepool在加入要釋放的對(duì)象時(shí)殃饿,底層調(diào)用的是objc_autoreleasePoolPush方法

  • autoreleasepool在調(diào)用析構(gòu)函數(shù)釋放時(shí)谋作,內(nèi)部的實(shí)現(xiàn)是調(diào)用objc_autoreleasePoolPop方法

底層分析

objc源碼中,對(duì)AutoreleasePool的解釋如下

Autorelease pool implementation

- A thread's autorelease pool is a stack of pointers. 
線程的自動(dòng)釋放池是指針的堆棧

- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
每個(gè)指針都是要釋放的對(duì)象壁晒,或者是POOL_BOUNDARY瓷们,它是自動(dòng)釋放池的邊界业栅。

- A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
池令牌是指向該池的POOL_BOUNDARY的指針秒咐。彈出池后,將釋放比哨點(diǎn)更熱的每個(gè)對(duì)象碘裕。

- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. 
堆棧分為兩個(gè)雙向鏈接的頁面列表携取。根據(jù)需要添加和刪除頁面。

- Thread-local storage points to the hot page, where newly autoreleased objects are stored. 
線程本地存儲(chǔ)指向熱頁面帮孔,該頁面存儲(chǔ)新自動(dòng)釋放的對(duì)象雷滋。

通過描述不撑,有以下幾點(diǎn)說明

  • 1、自動(dòng)釋放池 是一個(gè) 關(guān)于指針結(jié)構(gòu)

  • 2晤斩、其中的指針是指要釋放的對(duì)象或者 pool_boundary 哨兵(現(xiàn)在經(jīng)常被稱為 邊界

  • 3焕檬、自動(dòng)釋放池是一個(gè)的結(jié)構(gòu)(虛擬內(nèi)存中提及過) ,而且這個(gè)頁是一個(gè)雙向鏈表(表示有父節(jié)點(diǎn) 和 子節(jié)點(diǎn)澳泵,在類中提及過实愚,即類的繼承鏈)

  • 4、自動(dòng)釋放池和線程有關(guān)系

對(duì)于自動(dòng)釋放池兔辅,我們主要關(guān)心的點(diǎn)有以下三點(diǎn):

  • 1腊敲、自動(dòng)釋放池什么時(shí)候創(chuàng)建

  • 2维苔、對(duì)象是如何加入自動(dòng)釋放池的碰辅?

  • 3、哪些對(duì)象才會(huì)加入自動(dòng)釋放池介时?

下面帶著這些問題没宾,我們來一步步探索自動(dòng)釋放池的底層原理

AutoreleasePoolPage

  • 從最初的clang或者匯編分析我們了解了自動(dòng)釋放池其底層是調(diào)用的objc_autoreleasePoolPushobjc_autoreleasePoolPop兩個(gè)方法,其源碼實(shí)現(xiàn)如下
//***********push方法***********
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

//***********pop方法***********
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

  • 從源碼中我們可以發(fā)現(xiàn)潮尝,都是調(diào)用的AutoreleasePoolPagepushpop實(shí)現(xiàn)榕吼,以下是其定義,從定義中可以看出勉失,自動(dòng)釋放池是一個(gè)頁羹蚣,同時(shí)也是一個(gè)對(duì)象,這個(gè)頁的大小是4096字節(jié)
//************宏定義************
#define PAGE_MIN_SIZE           PAGE_SIZE
#define PAGE_SIZE               I386_PGBYTES
#define I386_PGBYTES            4096            /* bytes per 80386 page */

//************類定義************
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;

public:
    //頁的大小
    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif

private:

    ...

    //構(gòu)造函數(shù)
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),//開始存儲(chǔ)的位置
                                objc_thread_self(),//傳的是當(dāng)前線程乱凿,當(dāng)前線程時(shí)通過tls獲取的
                                newParent,
                                newParent ? 1+newParent->depth : 0,//如果是第一頁深度為0顽素,往后是前一個(gè)的深度+1
                                newParent ? newParent->hiwat : 0)
    {...}

    //析構(gòu)函數(shù)
    ~AutoreleasePoolPage() {...}

    ...

    //頁的開始位置
    id * begin() {...}

    //頁的結(jié)束位置
    id * end() {...}

    //頁是否為空
    bool empty() {...}

    //頁是否滿了
    bool full() {...}

    //頁的存儲(chǔ)是否少于一半
    bool lessThanHalfFull() {...}

     //添加釋放對(duì)象
    id *add(id obj){...}

    //釋放所有對(duì)象
    void releaseAll() {...}

    //釋放到stop位置之前的所有對(duì)象
    void releaseUntil(id *stop) {...}

    //殺掉
    void kill() {...}

    //釋放本地線程存儲(chǔ)空間
    static void tls_dealloc(void *p) {...}

    //獲取AutoreleasePoolPage
    static AutoreleasePoolPage *pageForPointer(const void *p) {...}
    static AutoreleasePoolPage *pageForPointer(uintptr_t p)  {...}

    //是否有空池占位符
    static inline bool haveEmptyPoolPlaceholder() {...}

    //設(shè)置空池占位符
    static inline id* setEmptyPoolPlaceholder(){...}

    //獲取當(dāng)前操作頁
    static inline AutoreleasePoolPage *hotPage(){...}

    //設(shè)置當(dāng)前操作頁
    static inline void setHotPage(AutoreleasePoolPage *page) {...}

    //獲取coldPage
    static inline AutoreleasePoolPage *coldPage() {...}

    //快速釋放
    static inline id *autoreleaseFast(id obj){...}

   //添加自動(dòng)釋放對(duì)象,當(dāng)頁滿的時(shí)候調(diào)用這個(gè)方法
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {...}

    //添加自動(dòng)釋放對(duì)象徒蟆,當(dāng)沒頁的時(shí)候使用這個(gè)方法
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj){...}

   //創(chuàng)建新頁
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj) {...}

public:
    //自動(dòng)釋放
    static inline id autorelease(id obj){...}

    //入棧
    static inline void *push() {...}

    //兼容老的 SDK 出棧方法
    __attribute__((noinline, cold))
    static void badPop(void *token){...}

    //出棧頁面
    template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop){...}
    __attribute__((noinline, cold))
    static void
    popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){...}

    //出棧
    static inline void
    pop(void *token){...}

    static void init(){...}

    //打印
    __attribute__((noinline, cold))
    void print(){...}

    //打印所有
    __attribute__((noinline, cold))
    static void printAll(){...}

    //打印Hiwat
    __attribute__((noinline, cold))
    static void printHiwat(){...}

  • 從其定義中發(fā)現(xiàn)胁出,AutoreleasePoolPage是繼承自AutoreleasePoolPageData,且該類的屬性也是來自父類,以下是AutoreleasePoolPageData的定義段审,
    • 發(fā)現(xiàn)其中有AutoreleasePoolPage對(duì)象全蝶,所以有以下一個(gè)關(guān)系鏈AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage,從這里可以說明自動(dòng)釋放池除了是一個(gè)頁寺枉,還是一個(gè)雙向鏈表結(jié)構(gòu)
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    //用來校驗(yàn)AutoreleasePoolPage的結(jié)構(gòu)是否完整
    magic_t const magic;//16個(gè)字節(jié)
    //指向最新添加的autoreleased對(duì)象的下一個(gè)位置抑淫,初始化時(shí)指向begin()
    __unsafe_unretained id *next;//8字節(jié)
    //指向當(dāng)前線程
    pthread_t const thread;//8字節(jié)
    //指向父節(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的parent值為nil
    AutoreleasePoolPage * const parent;//8字節(jié)
    //指向子節(jié)點(diǎn)姥闪,最后一個(gè)結(jié)點(diǎn)的child值為nil
    AutoreleasePoolPage *child;//8字節(jié)
    //表示深度始苇,從0開始,往后遞增1
    uint32_t const depth;//4字節(jié)
    //表示high water mark 最大入棧數(shù)量標(biāo)記
    uint32_t hiwat;//4字節(jié)

    //初始化
    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

其中AutoreleasePoolPageData結(jié)構(gòu)體的內(nèi)存大小為56字節(jié):

  • 屬性magic 的類型是magic_t結(jié)構(gòu)體筐喳,所占內(nèi)存大小為m[4];所占內(nèi)存(即4*4=16字節(jié))

  • 屬性next(指針)催式、thread(對(duì)象)函喉、parent(對(duì)象)、child(對(duì)象)均占8字節(jié)(即4*8=32字節(jié))

  • 屬性depth荣月、hiwat類型為uint32_t管呵,實(shí)際類型是unsigned int類型,均占4字節(jié)(即2*4=8字節(jié))

objc_autoreleasePoolPush 源碼分析

進(jìn)入push源碼實(shí)現(xiàn)哺窄,有以下邏輯

  • 判斷是否為有 pool

  • 如果沒有撇寞,則通過autoreleaseNewPage方法創(chuàng)建

  • 如果有,則通過autoreleaseFast壓棧哨兵對(duì)象

//入棧
static inline void *push() 
{
    id *dest;
    //判斷是否有pool
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.自動(dòng)釋放池從新池頁面開始
        //如果沒有堂氯,則創(chuàng)建
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        //壓棧一個(gè)POOL_BOUNDARY蔑担,即壓棧哨兵
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

1、創(chuàng)建頁 autoreleaseNewPage

  • 進(jìn)入objc_autoreleasePoolPush -> push -> autoreleaseNewPage源碼實(shí)現(xiàn)咽白,主要是通過hotPage`獲取當(dāng)前頁啤握,判斷當(dāng)前頁是否存在
    • 如果存在,則通過autoreleaseFullPage方法壓棧對(duì)象

    • 如果不存在,則通過autoreleaseNoPage方法創(chuàng)建頁

//創(chuàng)建新頁
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
    //獲取當(dāng)前操作頁
    AutoreleasePoolPage *page = hotPage();
    //如果存在,則壓棧對(duì)象
    if (page) return autoreleaseFullPage(obj, page);
    //如果不存在司草,則創(chuàng)建頁
    else return autoreleaseNoPage(obj);
}

//******** hotPage方法 ********
//獲取當(dāng)前操作頁
static inline AutoreleasePoolPage *hotPage() 
{
    //獲取當(dāng)前頁
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key);
    //如果是一個(gè)空池,則返回nil蹲蒲,否則,返回當(dāng)前線程的自動(dòng)釋放池
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    if (result) result->fastcheck();
    return result;
}

//******** autoreleaseNoPage方法 ********
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
    // "No page" could mean no pool has been pushed
    // or an empty placeholder pool has been pushed and has no contents yet
    ASSERT(!hotPage());

    bool pushExtraBoundary = false;
    //判斷是否是空占位符侵贵,如果是届搁,則壓棧哨兵標(biāo)識(shí)符置為YES
    if (haveEmptyPoolPlaceholder()) {
        // We are pushing a second pool over the empty placeholder pool
        // or pushing the first object into the empty placeholder pool.
        // Before doing that, push a pool boundary on behalf of the pool 
        // that is currently represented by the empty placeholder.
        pushExtraBoundary = true;
    }
    //如果對(duì)象不是哨兵對(duì)象,且沒有Pool窍育,則報(bào)錯(cuò)
    else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
        // We are pushing an object with no pool in place, 
        // and no-pool debugging was requested by environment.
        _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug", 
                     objc_thread_self(), (void*)obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return nil;
    }
    //如果對(duì)象是哨兵對(duì)象卡睦,且沒有申請(qǐng)自動(dòng)釋放池內(nèi)存,則設(shè)置一個(gè)空占位符存儲(chǔ)在tls中漱抓,其目的是為了節(jié)省內(nèi)存
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {//如果傳入?yún)?shù)為哨兵
        // We are pushing a pool with no pool in place,
        // and alloc-per-pool debugging was not requested.
        // Install and return the empty pool placeholder.
        return setEmptyPoolPlaceholder();//設(shè)置空的占位符
    }

    // We are pushing an object or a non-placeholder'd pool.

    // Install the first page.
    //初始化第一頁
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    //設(shè)置page為當(dāng)前聚焦頁
    setHotPage(page);

    // Push a boundary on behalf of the previously-placeholder'd pool.
    //壓棧哨兵的標(biāo)識(shí)符為YES表锻,則壓棧哨兵對(duì)象
    if (pushExtraBoundary) {
        //壓棧哨兵
        page->add(POOL_BOUNDARY);
    }

    // Push the requested object or pool.
    //壓棧對(duì)象
    return page->add(obj);
}

其中autoreleaseNoPage方法中發(fā)現(xiàn)當(dāng)前線程的自動(dòng)釋放池是通過AutoreleasePoolPage創(chuàng)建的,其定義中有構(gòu)造方法乞娄,而構(gòu)造方法的實(shí)現(xiàn)是通過父類AutoreleasePoolPageData的初始化方法(從上面的定義中可以得知)

//**********AutoreleasePoolPage構(gòu)造方法**********
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),//開始存儲(chǔ)的位置
                                objc_thread_self(),//傳的是當(dāng)前線程瞬逊,當(dāng)前線程時(shí)通過tls獲取的
                                newParent,
                                newParent ? 1+newParent->depth : 0,//如果是第一頁深度為0,往后是前一個(gè)的深度+1
                                newParent ? newParent->hiwat : 0)
{ 
    if (parent) {
        parent->check();
        ASSERT(!parent->child);
        parent->unprotect();
        //this 表示 新建頁面仪或,將當(dāng)前頁面的子節(jié)點(diǎn) 賦值為新建頁面
        parent->child = this;
        parent->protect();
    }
    protect();
}

//**********AutoreleasePoolPageData初始化方法**********
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }

其中AutoreleasePoolPageData方法傳入的參數(shù)含義為:

  • begin()表示壓棧的位置(即下一個(gè)要釋放對(duì)象的壓棧地址)确镊。可以通過源碼調(diào)試begin溶其,發(fā)現(xiàn)其具體實(shí)現(xiàn)等于頁首地址+56骚腥,其中的56就是結(jié)構(gòu)體AutoreleasePoolPageData的內(nèi)存大小

    image
//********begin()********
//頁的開始位置
id * begin() {
    //等于 首地址+56(AutoreleasePoolPage類所占內(nèi)存大卸丶洹)
    return (id *) ((uint8_t *)this+sizeof(*this));
}

  • objc_thread_self() 表示的是當(dāng)前線程瓶逃,而當(dāng)前線程時(shí)通過tls獲取的
__attribute__((const))
static inline pthread_t objc_thread_self()
{
    //通過tls獲取當(dāng)前線程
    return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}

  • newParent表示父節(jié)點(diǎn)

  • 后續(xù)兩個(gè)參數(shù)是通過父節(jié)點(diǎn)的深度束铭、最大入棧個(gè)數(shù)計(jì)算depth以及hiwat

查看自動(dòng)釋放池內(nèi)存結(jié)構(gòu)

由于在ARC模式下,是無法手動(dòng)調(diào)用autorelease厢绝,所以將Demo切換至MRC模式(Build Settings -> Objectice-C Automatic Reference Counting設(shè)置為NO

image
  • 定義如下代碼
//************打印自動(dòng)釋放池結(jié)構(gòu)************
extern void _objc_autoreleasePoolPrint(void);

//************運(yùn)行代碼************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //循環(huán)創(chuàng)建對(duì)象契沫,并加入自動(dòng)釋放池
        for (int i = 0; i < 5; i++) {
             NSObject *objc = [[NSObject alloc] sutorelease];
        }
        //調(diào)用
        _objc_autoreleasePoolPrint();
    }
}

運(yùn)行結(jié)果如下,發(fā)現(xiàn)是6個(gè)昔汉,但是我們壓棧的對(duì)象其實(shí)只有5個(gè)懈万,其中的POOL表示哨兵,即邊界靶病,其目的是為了防止越界

image

查看自動(dòng)釋放池的內(nèi)存結(jié)構(gòu)会通,發(fā)現(xiàn),頁的首地址與哨兵對(duì)象相差0x38娄周,轉(zhuǎn)換成十進(jìn)制剛好是56涕侈,也就是 AutoreleasePoolPage自己本身的內(nèi)存大小

  • 將上述的測(cè)試代碼的數(shù)據(jù)改為505,其內(nèi)存結(jié)構(gòu)如下煤辨,發(fā)現(xiàn)第一頁滿了裳涛,存儲(chǔ)了504個(gè)要釋放的對(duì)象,第二頁只存儲(chǔ)了一個(gè)

    image
  • 在將數(shù)據(jù)改為505+506众辨,來驗(yàn)證第二頁是否也是存儲(chǔ)504個(gè)對(duì)象

    image

    通過運(yùn)行發(fā)現(xiàn)端三,第一頁存儲(chǔ)504,第二頁存儲(chǔ)505鹃彻,第三頁存儲(chǔ)2個(gè)

結(jié)論

所以通過上述測(cè)試郊闯,可以得出以下結(jié)論:

  • 第一頁可以存放504個(gè)對(duì)象,且只有第一頁有哨兵蛛株,當(dāng)一頁壓棧滿了虚婿,就會(huì)開辟新的一頁

  • 第二頁開始,最多可以存放505個(gè)對(duì)象

  • 一頁的大小等于 505 * 8 = 4040

這個(gè)結(jié)論同樣可以通過AutoreleasePoolPage中的SIZE來得到印證泳挥,從其定義中我們可以得出然痊,一頁的大小是4096字節(jié),而在其構(gòu)造函數(shù)中對(duì)象的壓棧位置屉符,是從首地址+56開始的剧浸,所以可以一頁中實(shí)際可以存儲(chǔ)4096-56 = 4040字節(jié),轉(zhuǎn)換成對(duì)象是4040 / 8 = 505個(gè),即一頁最多可以存儲(chǔ)505個(gè)對(duì)象矗钟,其中第一頁有哨兵對(duì)象只能存儲(chǔ)504個(gè)唆香。其結(jié)構(gòu)圖示如下

image

面試題:哨兵在一個(gè)自動(dòng)釋放池有幾個(gè)?

  • 只有一個(gè)哨兵對(duì)象吨艇,且哨兵在第一頁

  • 第一頁最多可以存504個(gè)對(duì)象躬它,第二頁開始最多存 505個(gè)

2、壓棧對(duì)象 autoreleaseFast

  • 進(jìn)入autoreleaseFast源碼东涡,主要有以下幾步:
    • 獲取當(dāng)前操作頁冯吓,并判斷頁是否存在以及是否滿了

    • 如果頁存在倘待,且未滿,則通過add方法壓棧對(duì)象

    • 如果頁存在组贺,且滿了凸舵,則通過autoreleaseFullPage方法安排新的頁面

    • 如果頁不存在,則通過autoreleaseNoPage方法創(chuàng)建新頁

static inline id *autoreleaseFast(id obj)
{
    //獲取當(dāng)前操作頁
    AutoreleasePoolPage *page = hotPage();
    //判斷頁是否滿了
    if (page && !page->full()) {
        //如果未滿失尖,則壓棧
        return page->add(obj);
    } else if (page) {
        //如果滿了啊奄,則安排新的頁面
        return autoreleaseFullPage(obj, page);
    } else {
        //頁不存在,則新建頁
        return autoreleaseNoPage(obj);
    }
}

autoreleaseFullPage 方法

這個(gè)方法主要是用于判斷當(dāng)前頁是否已經(jīng)存儲(chǔ)滿了掀潮,如果當(dāng)前頁已經(jīng)滿了菇夸,通過do-while循環(huán)查找子節(jié)點(diǎn)對(duì)應(yīng)的頁,如果不存在仪吧,則新建頁峻仇,并壓棧對(duì)象

//添加自動(dòng)釋放對(duì)象,當(dāng)頁滿的時(shí)候調(diào)用這個(gè)方法
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    // The hot page is full. 
    // Step to the next non-full page, adding a new page if necessary.
    // Then add the object to that page.
    ASSERT(page == hotPage());
    ASSERT(page->full()  ||  DebugPoolAllocation);

    //do-while遍歷循環(huán)查找界面是否滿了
    do {
        //如果子頁面存在邑商,則將頁面替換為子頁面
        if (page->child) page = page->child;
        //如果子頁面不存在摄咆,則新建頁面
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    //設(shè)置為當(dāng)前操作頁面
    setHotPage(page);
    //對(duì)象壓棧
    return page->add(obj);
}

AutoreleasePoolPage初始化方法中可以看出,主要是通過操作child對(duì)象人断,將當(dāng)前頁的child指向新建頁面吭从,由此可以得出頁是通過雙向鏈表連接

add 方法

這個(gè)方法主要是添加釋放對(duì)象,其底層是實(shí)現(xiàn)是通過next指針存儲(chǔ)釋放對(duì)象恶迈,并將next指針遞增涩金,表示下一個(gè)釋放對(duì)象存儲(chǔ)的位置。從這里可以看出是通過棧結(jié)構(gòu)存儲(chǔ)

//添加釋放對(duì)象
id *add(id obj)
{
    ASSERT(!full());
    unprotect();
    //傳入對(duì)象存儲(chǔ)的位置
    id *ret = next;  // faster than `return next-1` because of aliasing
    //將obj壓棧到next指針位置暇仲,然后next進(jìn)行++步做,即下一個(gè)對(duì)象存儲(chǔ)的位置
    *next++ = obj;
    protect();
    return ret;
}

3、autorelease 底層分析

在demo中奈附,我們通過autorelease方法全度,在MRC模式下,將對(duì)象壓棧到自動(dòng)釋放池斥滤,下面來分析其底層實(shí)現(xiàn)

  • 查看autorelease方法源碼
    • 如果不是對(duì)象 或者 是小對(duì)象将鸵,則直接返回
    • 如果是對(duì)象,則調(diào)用對(duì)象的autorelease進(jìn)行釋放
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
    //如果不是對(duì)象佑颇,則直接返回
    if (!obj) return obj;
    //如果是小對(duì)象顶掉,也直接返回
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

  • 進(jìn)入對(duì)象的autorelease實(shí)現(xiàn)
??
inline id 
objc_object::autorelease()
{
    ASSERT(!isTaggedPointer());
    //判斷是否是自定義類
    if (fastpath(!ISA()->hasCustomRR())) {
        return rootAutorelease();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
??
inline id 
objc_object::rootAutorelease()
{
    //如果是小對(duì)象,直接返回
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
??
__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}
??
static inline id autorelease(id obj)
{
    ASSERT(obj);
    ASSERT(!obj->isTaggedPointer());
    //autoreleaseFast 壓棧操作
    id *dest __unused = autoreleaseFast(obj);
    ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}

從這里看出挑胸,無論是壓棧哨兵對(duì)象痒筒,還是普通對(duì)象,都會(huì)來到autoreleaseFast方法,只是區(qū)別標(biāo)識(shí)不同而以

objc_autoreleasePoolPop 源碼分析

objc_autoreleasePoolPop方法中有個(gè)參數(shù)簿透,在clang分析時(shí)移袍,發(fā)現(xiàn)傳入的參數(shù)是push壓棧后返回的哨兵對(duì)象,即ctxt萎战,其目的是避免出棧混亂舆逃,防止將別的對(duì)象出棧

  • 進(jìn)入pop源碼實(shí)現(xiàn)蚂维,主要由以下幾步
    • 空頁面的處理,并根據(jù)token獲取page

    • 容錯(cuò)處理

    • 通過popPage出棧頁

//出棧
static inline void
pop(void *token)
{
    AutoreleasePoolPage *page;
    id *stop;
   //判斷對(duì)象是否是空占位符
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        //如果當(dāng)是空占位符
        // Popping the top-level placeholder pool.
        //獲取當(dāng)前頁
        page = hotPage();
        if (!page) {
            // Pool was never used. Clear the placeholder.
            //如果當(dāng)前頁不存在路狮,則清除空占位符
            return setHotPage(nil);
        }
        // Pool was used. Pop its contents normally.
        // Pool pages remain allocated for re-use as usual.
        //如果當(dāng)前頁存在虫啥,則將當(dāng)前頁設(shè)置為coldPage,token設(shè)置為coldPage的開始位置
        page = coldPage();
        token = page->begin();
    } else {
        //獲取token所在的頁
        page = pageForPointer(token);
    }

    stop = (id *)token;
    //判斷最后一個(gè)位置,是否是哨兵
    if (*stop != POOL_BOUNDARY) {
        //最后一個(gè)位置不是哨兵奄妨,即最后一個(gè)位置是一個(gè)對(duì)象
        if (stop == page->begin()  &&  !page->parent) {
            //如果是第一個(gè)位置涂籽,且沒有父節(jié)點(diǎn),什么也不做
            // Start of coldest page may correctly not be POOL_BOUNDARY:
            // 1\. top-level pool is popped, leaving the cold page in place
            // 2\. an object is autoreleased with no pool
        } else {
            //如果是第一個(gè)位置砸抛,且有父節(jié)點(diǎn)评雌,則出現(xiàn)了混亂
            // Error. For bincompat purposes this is not 
            // fatal in executables built with old SDKs.
            return badPop(token);
        }
    }

    if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
        return popPageDebug(token, page, stop);
    }
    //出棧頁
    return popPage<false>(token, page, stop);
}

  • 進(jìn)入popPage源碼,其中傳入的allowDebug為false直焙,則通過releaseUntil出棧當(dāng)前頁stop位置之前的所有對(duì)象景东,即向棧中的對(duì)象發(fā)送release消息,直到遇到傳入的哨兵對(duì)象
//出棧頁面
template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    if (allowDebug && PrintPoolHiwat) printHiwat();
    //出棧當(dāng)前操作頁面對(duì)象
    page->releaseUntil(stop);

    // memory: delete empty children 刪除空子項(xiàng)
    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        //調(diào)試期間刪除每個(gè)特殊情況下的所有池
        //獲取當(dāng)前頁面的父節(jié)點(diǎn)
        AutoreleasePoolPage *parent = page->parent;
        //將當(dāng)前頁面殺掉
        page->kill();
        //設(shè)置操作頁面為父節(jié)點(diǎn)頁面
        setHotPage(parent);
    }
    else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        // special case: delete everything for pop(top)
        // when debugging missing autorelease pools
        //特殊情況:調(diào)試丟失的自動(dòng)釋放池時(shí)刪除pop(top)的所有內(nèi)容
        page->kill();
        setHotPage(nil);
    }
    else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full 如果頁面已滿一半以上奔誓,則保留一個(gè)空子級(jí)
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

  • 進(jìn)入releaseUntil實(shí)現(xiàn)斤吐,主要是通過循環(huán)遍歷,判斷對(duì)象是否等于stop厨喂,其目的是釋放stop之前的所有的對(duì)象和措,
    • 首先通過獲取page的next釋放對(duì)象(即page的最后一個(gè)對(duì)象),并對(duì)next進(jìn)行遞減蜕煌,獲取上一個(gè)對(duì)象

    • 判斷是否是哨兵對(duì)象派阱,如果不是則自動(dòng)調(diào)用objc_release釋放

//釋放到stop位置之前的所有對(duì)象
void releaseUntil(id *stop) 
{
    // Not recursive: we don't want to blow out the stack  不是遞歸的:我們不想破壞堆棧
    // if a thread accumulates a stupendous amount of garbage
    //判斷下一個(gè)對(duì)象是否等于stop,如果不等于斜纪,則進(jìn)入while循環(huán)
    while (this->next != stop) {
        // Restart from hotPage() every time, in case -release 
        // autoreleased more objects 每次從hotPage()重新啟動(dòng)颁褂,以防-release自動(dòng)釋放更多對(duì)象
        //獲取當(dāng)前操作頁面,即hot頁面
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        //如果當(dāng)前頁是空的
        while (page->empty()) {
            //將page賦值為父節(jié)點(diǎn)頁
            page = page->parent;
            //并設(shè)置當(dāng)前頁為父節(jié)點(diǎn)頁
            setHotPage(page);
        }

        page->unprotect();
        //next進(jìn)行--操作傀广,即出棧
        id obj = *--page->next;
        //將頁索引位置置為SCRIBBLE颁独,表示已經(jīng)被釋放
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_BOUNDARY) {
            //釋放
            objc_release(obj);
        }
    }
    //設(shè)置當(dāng)前頁
    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        ASSERT(page->empty());
    }
#endif
}

  • 進(jìn)入kill實(shí)現(xiàn),主要是銷毀當(dāng)前頁伪冰,將當(dāng)前頁賦值為父節(jié)點(diǎn)頁誓酒,并將父節(jié)點(diǎn)頁的child對(duì)象指針置為nil
//銷毀
void kill() 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    AutoreleasePoolPage *page = this;
    //獲取最后一個(gè)頁
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        //子節(jié)點(diǎn) 變成 父節(jié)點(diǎn)
        page = page->parent;
        if (page) {
            page->unprotect();
            //子節(jié)點(diǎn)為nil
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

總結(jié)

通過上面的分析,針對(duì)自動(dòng)釋放池的push和pop,總結(jié)如下

  • 在自動(dòng)釋放池的壓棧(即push)操作中
    • 當(dāng)沒有pool靠柑,即只有空占位符(存儲(chǔ)在tls中)時(shí)寨辩,則創(chuàng)建頁,壓棧哨兵對(duì)象
    • 在頁中壓棧普通對(duì)象主要是通過next指針遞增進(jìn)行的歼冰,
    • 當(dāng)頁滿了時(shí)靡狞,需要設(shè)置頁的child對(duì)象為新建頁

所以,綜上所述隔嫡,autoreleaseobjc_autoreleasePush的整體底層的流程如下圖所示

image
  • 在自動(dòng)釋放池的出棧(即pop)操作中
    • 在頁中出棧普通對(duì)象主要是通過next指針遞減進(jìn)行的甸怕,
    • 當(dāng)頁空了時(shí),需要賦值頁的parent對(duì)象為當(dāng)前頁

綜上所述腮恩,objc_autoreleasePoolPop出棧的地城流程如下所示

image

RunLoop

對(duì)于RunLoop梢杭,主要關(guān)心的點(diǎn)有以下幾個(gè)

  • 1、runloop是什么秸滴?

  • 2武契、runloop和線程的關(guān)系?

  • 3荡含、runloop是什么時(shí)候創(chuàng)建的咒唆?

1、RunLoop介紹

RunLoop是事件接收和分發(fā)機(jī)制的一個(gè)實(shí)現(xiàn)释液,是線程相關(guān)的基礎(chǔ)框架的一部分钧排,一個(gè)RunLoop就是一個(gè)事件處理的循環(huán),用來不停的調(diào)度工作以及處理輸入事件均澳。

RunLoop本質(zhì)是一個(gè) do-while循環(huán)恨溜,沒事做就休息,來活了就干活找前。與普通的while循環(huán)是有區(qū)別的糟袁,普通的while循環(huán)會(huì)導(dǎo)致CPU進(jìn)入忙等待狀態(tài),即一直消耗cpu躺盛,而RunLoop則不會(huì)项戴,RunLoop是一種閑等待,即RunLoop具備休眠功能槽惫。

RunLoop的作用

  • 保持程序的持續(xù)運(yùn)行

  • 處理App中的各種事件(觸摸周叮、定時(shí)器、performSelector)

  • 節(jié)省cpu資源界斜,提供程序的性能仿耽,該做事就做事,該休息就休息

RunLoop 源碼分析

RunLoop源碼的下載地址各薇,在其中找到最新版下載即可

2项贺、RunLoop和線程的關(guān)系

一般在日常開發(fā)中君躺,對(duì)于RunLoop的獲取主要有以下兩種方式

// 主運(yùn)行循環(huán)
 CFRunLoopRef mainRunloop = CFRunLoopGetMain();
 // 當(dāng)前運(yùn)行循環(huán)
 CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();

  • 進(jìn)入CFRunLoopGetMain源碼
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    //pthread_main_thread_np 主線程
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

  • 進(jìn)入_CFRunLoopGet0
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //如果t不存在,則標(biāo)記為主線程(即默認(rèn)情況开缎,默認(rèn)是主線程)
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);

        //創(chuàng)建全局字典棕叫,標(biāo)記為kCFAllocatorSystemDefault
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //通過主線程 創(chuàng)建主運(yùn)行循環(huán)
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //利用dict,進(jìn)行key-value綁定操作奕删,即可以說明俺泣,線程和runloop是一一對(duì)應(yīng)的
        // dict : key value
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }

        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    //通過其他線程獲取runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        //如果沒有獲取到,則新建一個(gè)運(yùn)行循環(huán)
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //將新建的runloop 與 線程進(jìn)行key-value綁定
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

從這里可以說明完残,Runloop只有兩種伏钠,一種是主線程的, 一個(gè)是其他線程的坏怪。即runloop和線程是一一對(duì)應(yīng)的

3贝润、RunLoop的創(chuàng)建

  • 進(jìn)入__CFRunLoopCreate源碼绊茧,其中主要是對(duì)runloop屬性的賦值操作
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
    //如果loop為空铝宵,則直接返回NULL
    if (NULL == loop) {
        return NULL;
    }
    //runloop屬性配置
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}

  • 進(jìn)入CFRunLoopRef的定義,根據(jù)定義得知,其實(shí)RunLoop也是一個(gè)對(duì)象华畏。是__CFRunLoop結(jié)構(gòu)體的指針類型
typedef struct __CFRunLoop * CFRunLoopRef;
??
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

從定義中可以得出鹏秋,一個(gè)RunLoop依賴于多個(gè)Mode,意味著一個(gè)RunLoop需要處理多個(gè)事務(wù)亡笑,即一個(gè)Mode對(duì)應(yīng)多個(gè)Item侣夷,而一個(gè)item中,包含了timer仑乌、source百拓、observer,如下所示

image

Mode類型
其中mode在蘋果文檔中提及的有五個(gè)晰甚,而在iOS中公開暴露出來的只有 NSDefaultRunLoopModeNSRunLoopCommonModes衙传。 NSRunLoopCommonModes 實(shí)際上是一個(gè) Mode 的集合,默認(rèn)包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode厕九。

  • NSDefaultRunLoopMode默認(rèn)的mode蓖捶,正常情況下都是在這個(gè)mode

  • NSConnectionReplyMode

  • NSModalPanelRunLoopMode

  • NSEventTrackingRunLoopMode:使用這個(gè)Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動(dòng))

  • NSRunLoopCommonModes:偽模式,靈活性更好

Source & Timer & Observer

  • Source表示可以喚醒RunLoop的一些事件扁远,例如用戶點(diǎn)擊了屏幕俊鱼,就會(huì)創(chuàng)建一個(gè)RunLoop,主要分為Source0Source1

    • Source0 表示 非系統(tǒng)事件畅买,即用戶自定義的事件

    • Source1 表示系統(tǒng)事件并闲,主要負(fù)責(zé)底層的通訊,具備喚醒能力

  • Timer 就是常用NSTimer定時(shí)器這一類

  • Observer 主要用于監(jiān)聽RunLoop的狀態(tài)變化谷羞,并作出一定響應(yīng)焙蚓,主要有以下一些狀態(tài)

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    //進(jìn)入RunLoop
    kCFRunLoopEntry = (1UL << 0),
    //即將處理Timers
    kCFRunLoopBeforeTimers = (1UL << 1),
    //即將處理Source
    kCFRunLoopBeforeSources = (1UL << 2),
    //即將進(jìn)入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),
    //被喚醒
    kCFRunLoopAfterWaiting = (1UL << 6),
    //退出RunLoop
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

驗(yàn)證:RunLoop和mode是一對(duì)多
下面,通過上面的代碼調(diào)試來驗(yàn)證我們上面提及的關(guān)系

  • 通過lldb命令獲取mainRunloopcurrentRunloopcurrentMode

    • po CFRunLoopCopyCurrentMode(mainRunloop)

    • po CFRunLoopCopyCurrentMode(currentRunloop)

      image

      從這里购公,可以說明萌京,runloop在運(yùn)行時(shí)的mode只有一個(gè)

  • 獲取mainRunloop的所有模型,即po CFRunLoopCopyAllModes(mainRunloop)

    image

    從結(jié)果可以驗(yàn)證runloopCFRunloopMode 具有 一對(duì)多的關(guān)系

驗(yàn)證:mode和Item也是一對(duì)多

  • 在上述代碼中宏浩,加斷點(diǎn)知残,通過bt查看堆棧信息,從這里看出timer的item類型如下所示

    image
  • RunLoop源碼中查看Item類型比庄,有以下幾種

    • block應(yīng)用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

    • 調(diào)用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

    • 響應(yīng)source0: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

    • 響應(yīng)source1: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

    • GCD主隊(duì)列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

    • observer源: __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

      image
  • 在這里以Timer為例求妹,一般初始化timer時(shí),都會(huì)將timer通過addTimer:forMode:方法添加到Runloop中,于是在源碼中查找addTimer的相關(guān)方法纫溃,即CFRunLoopAddTimer方法敬惦,其源碼實(shí)現(xiàn)如下,其實(shí)現(xiàn)主要判斷是否是kCFRunLoopCommonModes净神,然后查找runloop的mode進(jìn)行匹配處理

    • 其中kCFRunLoopCommonModes 不是一種模式,是一種抽象的偽模式溉委,比defaultMode更加靈活
    • 通過CFSetAddValue(rl->_commonModeItems, rlt);可以得知鹃唯,runloopmode一對(duì)多的,同時(shí)可以得出modeitem 也是一對(duì)多
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);

    // 重點(diǎn) : kCFRunLoopCommonModes
    if (modeName == kCFRunLoopCommonModes) {
        //如果是kCFRunLoopCommonModes 類型

        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;

        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //runloop與mode 是一對(duì)多的瓣喊, mode與item也是一對(duì)多的
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            //執(zhí)行
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
        //如果是非commonMode類型
        //查找runloop的模型
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
        }
        //判斷mode是否匹配
        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
                rlt->_runLoop = rl;
            } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                return;
            }
            // 如果匹配坡慌,則將runloop加進(jìn)去,而runloop的執(zhí)行依賴于  [runloop run]
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }

    __CFRunLoopUnlock(rl);
}

4藻三、RunLoop執(zhí)行

眾所周知洪橘,RunLoop的執(zhí)行依賴于run方法,從下面的堆棧信息中可以看出棵帽,其底層執(zhí)行的是__CFRunLoopRun方法

image
  • 進(jìn)入__CFRunLoopRun源碼熄求,針對(duì)不同的對(duì)象,有不同的處理
    • 如果有observer岖寞,則調(diào)用 __CFRunLoopDoObservers

    • 如果有block抡四,則調(diào)用__CFRunLoopDoBlocks

    • 如果有timer,則調(diào)用 __CFRunLoopDoTimers

    • 如果是source0仗谆,則調(diào)用__CFRunLoopDoSources0

    • 如果是source1指巡,則調(diào)用__CFRunLoopDoSource1

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    ...

    do{
        ...
         //通知 Observers: 即將處理timer事件
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //通知 Observers: 即將處理Source事件
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        //處理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        //處理sources0返回為YES
        if (sourceHandledThisLoop) {
            // 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        ...

        //如果是timer
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }

        ...

        //如果是source1
        CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
        if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            mach_msg_header_t *reply = NULL;
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
            if (NULL != reply) {
                (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
        }
        ...

    }while (0 == retVal);

    ...
}

  • 進(jìn)入__CFRunLoopDoTimers源碼,主要是通過for循環(huán)隶垮,對(duì)單個(gè)timer進(jìn)行處理
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    ...
    //循環(huán)遍歷藻雪,做下層單個(gè)timer的執(zhí)行
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    ...
}

  • 進(jìn)入__CFRunLoopDoTimer源碼,主要邏輯是timer執(zhí)行完畢后狸吞,會(huì)主動(dòng)調(diào)用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__函數(shù)勉耀,正好與timer堆棧調(diào)用中的一致

    image
// mode and rl are locked on entry and exit
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { 
    ...
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
    ...
}

timer執(zhí)行總結(jié)

  • 為自定義的timer指煎,設(shè)置Mode,并將其加入RunLoop

  • 在RunLoop的run方法執(zhí)行時(shí)便斥,會(huì)調(diào)用__CFRunLoopDoTimers執(zhí)行所有timer

  • __CFRunLoopDoTimers方法中至壤,會(huì)通過for循環(huán)執(zhí)行單個(gè)timer的操作

  • __CFRunLoopDoTimer方法中,timer執(zhí)行完畢后枢纠,會(huì)執(zhí)行對(duì)應(yīng)的timer回調(diào)函數(shù)

以上像街,是針對(duì)timer的執(zhí)行分析,對(duì)于observer晋渺、block镰绎、source0、source1木西,其執(zhí)行原理與timer是類似的畴栖,這里就不再重復(fù)說明以下是蘋果官方文檔針對(duì)RunLoop處理不同源的圖示

image

5、RunLoop 底層原理

從上述的堆棧信息中可以看出八千,run在底層的實(shí)現(xiàn)路徑為CFRunLoopRun -> CFRunLoopRun -> __CFRunLoopRun

  • 進(jìn)入CFRunLoopRun源碼吗讶,其中傳入的參數(shù)1.0e10(科學(xué)計(jì)數(shù)) 等于 1* e^10,用于表示超時(shí)時(shí)間
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        // 1.0e10 : 科學(xué)技術(shù) 1*10^10
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

  • 進(jìn)入CFRunLoopRunSpecific源碼叼丑,关翎,首先根據(jù)modeName找到對(duì)應(yīng)的mode扛门,然后主要分為三種情況:
    • 如果是entry鸠信,則通知observer,即將進(jìn)入runloop

    • 如果是exit论寨,則通過observer星立,即將退出runloop

    • 如果是其他中間狀態(tài),主要是通過runloop處理各種源

其偽代碼表示如下

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);

    //首先根據(jù)modeName找到對(duì)應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

    // 通知 Observers: RunLoop 即將進(jìn)入 loop葬凳。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    // 內(nèi)部函數(shù)绰垂,進(jìn)入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    // 通知 Observers: RunLoop 即將退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;

}

  • 進(jìn)入__CFRunLoopRun源碼火焰,由于這部分代碼較多劲装,于是這里用偽代碼代替。其主要邏輯是根據(jù)不同的事件源進(jìn)行不同的處理昌简,當(dāng)RunLoop休眠時(shí)占业,可以通過相應(yīng)的事件喚醒RunLoop
//核心函數(shù)
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){

    //通過GCD開啟一個(gè)定時(shí)器,然后開始跑圈
    dispatch_source_t timeout_timer = NULL;
    ...
    dispatch_resume(timeout_timer);

    int32_t retVal = 0;

    //處理事務(wù),即處理items
    do {

        // 通知 Observers: 即將處理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

        // 通知 Observers: 即將處理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)

        // 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        // 處理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

        // 處理sources0返回為YES
        if (sourceHandledThisLoop) {
            // 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        // 判斷有無端口消息(Source1)
        if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
            // 處理消息
            goto handle_msg;
        }

        // 通知 Observers: 即將進(jìn)入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);

        // 等待被喚醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);

        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);

        // 通知 Observers: 被喚醒纯赎,結(jié)束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    handle_msg:
        if (被timer喚醒) {
            // 處理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())谦疾;
        }else if (被GCD喚醒){
            // 處理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        }else if (被source1喚醒){
            // 被Source1喚醒,處理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }

        // 處理block
        __CFRunLoopDoBlocks(rl, rlm);

        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;//處理源
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;//超時(shí)
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;//停止
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;//停止
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;//結(jié)束
        }

    }while (0 == retVal);

    return retVal;
}

所以犬金,綜上所述念恍,RunLoop的執(zhí)行流程六剥,如下所示

image

相關(guān)面試題

AutoreleasePool 相關(guān)

面試題1:臨時(shí)變量什么時(shí)候釋放?

  • 如果在正常情況下峰伙,一般是超出其作用域就會(huì)立即釋放

  • 如果將臨時(shí)變量加入了自動(dòng)釋放池疗疟,會(huì)延遲釋放,即在runloop休眠或者autoreleasepool作用域之后釋放

面試題2:AutoreleasePool原理

  • 自動(dòng)釋放池的本質(zhì)是一個(gè)AutoreleasePoolPage結(jié)構(gòu)體對(duì)象瞳氓,是一個(gè)棧結(jié)構(gòu)存儲(chǔ)的頁秃嗜,每一個(gè)AutoreleasePoolPage都是以雙向鏈表的形式連接

  • 自動(dòng)釋放池的壓棧出棧主要是通過結(jié)構(gòu)體的構(gòu)造函數(shù)和析構(gòu)函數(shù)調(diào)用底層的objc_autoreleasePoolPushobjc_autoreleasePoolPop,實(shí)際上是調(diào)用AutoreleasePoolPagepushpop兩個(gè)方法

  • 每次調(diào)用push操作其實(shí)就是創(chuàng)建一個(gè)新的AutoreleasePoolPage顿膨,而AutoreleasePoolPage的具體操作就是插入一個(gè)POOL_BOUNDARY锅锨,并返回插入POOL_BOUNDARY的內(nèi)存地址。而push內(nèi)部調(diào)用autoreleaseFast方法處理恋沃,主要有以下三種情況

    • 當(dāng)page存在必搞,且不滿時(shí),調(diào)用add方法將對(duì)象添加至page的next指針處囊咏,并next遞增

    • 當(dāng)page存在恕洲,且已滿時(shí),調(diào)用autoreleaseFullPage初始化一個(gè)新的page梅割,然后調(diào)用add方法將對(duì)象添加至page棧中

    • 當(dāng)page不存在時(shí)霜第,調(diào)用autoreleaseNoPage創(chuàng)建一個(gè)hotPage,然后調(diào)用add方法將對(duì)象添加至page棧中

  • 當(dāng)執(zhí)行pop操作時(shí)户辞,會(huì)傳入一個(gè)值泌类,這個(gè)值就是push操作的返回值,即POOL_BOUNDARY的內(nèi)存地址token底燎。所以pop內(nèi)部的實(shí)現(xiàn)就是根據(jù)token找到哨兵對(duì)象所處的page中刃榨,然后使用 objc_release 釋放 token之前的對(duì)象,并把next 指針到正確位置

面試題3:AutoreleasePool能否嵌套使用双仍?

  • 可以嵌套使用枢希,其目的是可以控制應(yīng)用程序的內(nèi)存峰值,使其不要太高

  • 可以嵌套的原因是因?yàn)樽詣?dòng)釋放池是以棧為節(jié)點(diǎn)朱沃,通過雙向鏈表的形式連接的苞轿,且是和線程一一對(duì)應(yīng)的

  • 自動(dòng)釋放池的多層嵌套其實(shí)就是不停的pushs哨兵對(duì)象,在pop時(shí)逗物,會(huì)先釋放里面的搬卒,在釋放外面的

面試題4:哪些對(duì)象可以加入AutoreleasePool?alloc創(chuàng)建可以嗎敬察?

  • 使用new秀睛、alloc、copy關(guān)鍵字生成的對(duì)象和retain了的對(duì)象需要手動(dòng)釋放莲祸,不會(huì)被添加到自動(dòng)釋放池中

  • 設(shè)置為autorelease的對(duì)象不需要手動(dòng)釋放蹂安,會(huì)直接進(jìn)入自動(dòng)釋放池

  • 所有 autorelease 的對(duì)象椭迎,在出了作用域之后,會(huì)被自動(dòng)添加到最近創(chuàng)建的自動(dòng)釋放池中

面試題5:AutoreleasePool的釋放時(shí)機(jī)是什么時(shí)候田盈?

  • App 啟動(dòng)后畜号,蘋果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()允瞧。

  • 第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入 Loop)简软,其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池。其 order 是 -2147483647述暂,優(yōu)先級(jí)最高痹升,保證創(chuàng) 建釋放池發(fā)生在其他所有回調(diào)之前。

  • 第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即 將退出 Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池畦韭。這個(gè) Observer 的 order 是 2147483647疼蛾,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后艺配。

面試題6:thread 和 AutoreleasePool的關(guān)系

官方文檔中察郁,找到如下說明

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.

大致意思如下:

  • 每個(gè)線程,包括主線程在內(nèi)都維護(hù)了自己的自動(dòng)釋放池堆棧結(jié)構(gòu)

  • 新的自動(dòng)釋放池在被創(chuàng)建時(shí)转唉,會(huì)被添加到棧頂皮钠;當(dāng)自動(dòng)釋放池銷毀時(shí),會(huì)從棧中移除

  • 對(duì)于當(dāng)前線程來說赠法,會(huì)將自動(dòng)釋放的對(duì)象放入自動(dòng)釋放池的棧頂麦轰;在線程停止時(shí),會(huì)自動(dòng)釋放掉與該線程關(guān)聯(lián)的所有自動(dòng)釋放池

總結(jié):每個(gè)線程都有與之關(guān)聯(lián)的自動(dòng)釋放池堆棧結(jié)構(gòu)期虾,新的pool在創(chuàng)建時(shí)會(huì)被壓棧到棧頂原朝,pool銷毀時(shí)驯嘱,會(huì)被出棧镶苞,對(duì)于當(dāng)前線程來說,釋放對(duì)象會(huì)被壓棧到棧頂鞠评,線程停止時(shí)茂蚓,會(huì)自動(dòng)釋放與之關(guān)聯(lián)的自動(dòng)釋放池

面試題7:RunLoop 和 AutoreleasePool的關(guān)系
官方文檔中,找到如下說明

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

大致意思如下:

  • 主程序的RunLoop在每次事件循環(huán)之前之前剃幌,會(huì)自動(dòng)創(chuàng)建一個(gè) autoreleasePool

  • 并且會(huì)在事件循環(huán)結(jié)束時(shí)聋涨,執(zhí)行drain操作,釋放其中的對(duì)象

RunLoop相關(guān)

面試題1

當(dāng)前有個(gè)子線程负乡,子線程中有個(gè)timer牍白。timer是否能夠執(zhí)行 并進(jìn)行持續(xù)的打印抖棘?

 CJLThread *thread = [[CJLThread alloc] initWithBlock:^{

        // thread.name = nil 因?yàn)檫@個(gè)變量只是捕捉
        // CJLThread *thread = nil
        // thread = 初始化 捕捉一個(gè)nil進(jìn)來
        NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"hello word");            // 退出線程--結(jié)果runloop也停止了
            if (self.isStopping) {
                [NSThread exit];
            }
        }];
    }];

    thread.name = @"lgcode.com";
    [thread start];

  • 不可以茂腥,因?yàn)?code>子線程的runloop默認(rèn)不啟動(dòng)狸涌, 需要runloop run啟動(dòng),需要將上述代碼改成下面這樣:
//改成
 CJLThread *thread = [[CJLThread alloc] initWithBlock:^{

    // thread.name = nil 因?yàn)檫@個(gè)變量只是捕捉
    // CJLThread *thread = nil
    // thread = 初始化 捕捉一個(gè)nil進(jìn)來
    NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"hello word");            // 退出線程--結(jié)果runloop也停止了
        if (self.isStopping) {
            [NSThread exit];
        }
    }];
     [[NSRunLoop currentRunLoop] run];
}];

thread.name = @"lgcode.com";
[thread start];

面試題2:RunLoop和線程的關(guān)系

  • 每個(gè)線程都有一個(gè)與之對(duì)應(yīng)的RunLoop最岗,所以RunLoop與線程是一一對(duì)應(yīng)的帕胆,其綁定關(guān)系通過一個(gè)全局的DIctionary存儲(chǔ),線程為key般渡,runloop為value懒豹。

  • 線程中的RunLoop主要是用來管理線程的,當(dāng)線程的RunLoop開啟后驯用,會(huì)在執(zhí)行完任務(wù)后進(jìn)行休眠狀態(tài)脸秽,當(dāng)有事件觸發(fā)喚醒時(shí),又開始工作蝴乔,即有活時(shí)干活豹储,沒活就休息

  • 主線程RunLoop默認(rèn)開啟的,在程序啟動(dòng)之后淘这,會(huì)一直運(yùn)行剥扣,不會(huì)退出

  • 其他線程的RunLoop默認(rèn)是不開啟的,如果需要铝穷,則手動(dòng)開啟

面試3:NSRunLoop 和 CFRunLoopRef 區(qū)別

  • NSRunLoop是基于CFRunLoopRef面向?qū)ο蟮腁PI钠怯,是不安全

  • CFRunLoopRef是基于C語言,是線程安全

面試4:Runloop的mode作用是什么曙聂?

mode主要是用于指定RunLoop中事件優(yōu)先級(jí)的

面試5:以+scheduledTimerWithTimeInterval:的方式觸發(fā)的timer晦炊,在滑動(dòng)頁面上的列表時(shí),timer會(huì)暫湍梗回調(diào)断国, 為什么?如何解決榆苞?

  • timer停止的原因是因?yàn)榛瑒?dòng)scrollView時(shí)稳衬,主線程的RunLoop會(huì)從NSDefaultRunLoopMode切換到UITrackingRunLoopMode,而timer是添加在NSDefaultRunLoopMode坐漏。所以timer不會(huì)執(zhí)行

  • timer放入NSRunLoopCommonModes中執(zhí)行

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末薄疚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子赊琳,更是在濱河造成了極大的恐慌街夭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躏筏,死亡現(xiàn)場(chǎng)離奇詭異板丽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)趁尼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門埃碱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碴卧,“玉大人,你說我怎么就攤上這事乃正∽〔幔” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵瓮具,是天一觀的道長荧飞。 經(jīng)常有香客問我,道長名党,這世上最難降的妖魔是什么叹阔? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮传睹,結(jié)果婚禮上耳幢,老公的妹妹穿的比我還像新娘。我一直安慰自己欧啤,他們只是感情好睛藻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邢隧,像睡著了一般店印。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倒慧,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天按摘,我揣著相機(jī)與錄音,去河邊找鬼纫谅。 笑死炫贤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的付秕。 我是一名探鬼主播兰珍,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼盹牧!你這毒婦竟也來了俩垃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤汰寓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后苹粟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體有滑,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年嵌削,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毛好。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片望艺。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肌访,靈堂內(nèi)的尸體忽然破棺而出找默,到底是詐尸還是另有隱情,我是刑警寧澤吼驶,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布惩激,位于F島的核電站,受9級(jí)特大地震影響蟹演,放射性物質(zhì)發(fā)生泄漏风钻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一酒请、第九天 我趴在偏房一處隱蔽的房頂上張望骡技。 院中可真熱鬧,春花似錦羞反、人聲如沸布朦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喝滞。三九已至,卻和暖如春膏秫,著一層夾襖步出監(jiān)牢的瞬間右遭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工缤削, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窘哈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓亭敢,卻偏偏與公主長得像滚婉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帅刀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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