自動釋放池 & Runloop

前言

本篇文章會大致分析下自動釋放池(AutoreleasePool)Runloop的底層實(shí)現(xiàn)原理量没,這兩個知識點(diǎn)也是面試中經(jīng)常問到的慢显,希望大家都能掌握這些內(nèi)容,同時开镣,有不對的地方刀诬,希望大家及時指正,謝謝~

一邪财、自動釋放池

AutoreleasePool是OC中的一種內(nèi)存自動回收機(jī)制陕壹,它可以將加入AutoreleasePool中的變量release的時機(jī)延遲,簡單來說树埠,就是當(dāng)創(chuàng)建一個對象糠馆,正常情況下,變量會在其作用域的結(jié)束時會立即release釋放怎憋。如果將該對象加入到了AutoreleasePool中又碌,即使作用域結(jié)束了,這個對象并不會立即釋放绊袋,它會等到runloop休眠或超出autoreleasepool作用域{}之后才會被釋放毕匀。其機(jī)制如下圖所示??

  1. 從程序App啟動dyld加載完成后,主線程對應(yīng)的runloop會處于休眠狀態(tài)愤炸,等待用戶交互喚醒runloop期揪;
  2. 用戶的每一次交互都會啟動一次runloop,用于處理用戶的所有點(diǎn)擊规个、觸摸事件等;
  3. CocoaTouch在監(jiān)聽到交互事件后姓建,就會創(chuàng)建自動釋放池诞仓,并將所有延遲釋放的對象添加到自動釋放池中;
  4. 在一次完整runloop結(jié)束之前速兔,CocoaTouch會向自動釋放池中所有對象發(fā)送release消息墅拭,然后銷毀自動釋放池。

1.1 找入口

那AutoreleasePool對應(yīng)的底層到底是什么東西呢涣狗?首先需要找到入口谍婉,我們先clang一下看看@autoreleasepool對應(yīng)的C++代碼是什么??

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"來了,老弟");

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));;
    }
}

xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m

我們發(fā)現(xiàn),@autoreleasepool對應(yīng)的底層結(jié)構(gòu)是__AtAutoreleasePool镀钓,我們繼續(xù)在.cpp中搜索__AtAutoreleasePool??

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

可見穗熬,__AtAutoreleasePool是一個結(jié)構(gòu)體,包含構(gòu)造函數(shù)丁溅,析構(gòu)函數(shù)和一個指針atautoreleasepoolobj唤蔗。
接下來,我們再打斷點(diǎn)??

看看匯編代碼??

定位到了objc_autoreleasePoolPushobjc_autoreleasePoolPop。這兩個函數(shù)就是@autoreleasepool的作用域{}對應(yīng)底層的壓棧出棧妓柜。

1.2 底層解析

通過上面對AutoreleasePool入口的定位箱季,我們知道了objc_autoreleasePoolPushobjc_autoreleasePoolPop,現(xiàn)在我們?nèi)サ絆bjc源碼棍掐,首先搜索objc_autoreleasePoolPush藏雏,看看??

定位到是類AutoreleasePoolPage,再搜索這個類??

通過注釋作煌,有以下幾點(diǎn)說明

  1. 自動釋放池是一個指針集合掘殴,指向一個結(jié)構(gòu)空間。
  2. 指針集合中的指針是指向要被釋放的對象或者pool_boundary(之前叫做哨兵最疆,現(xiàn)在經(jīng)常被稱為邊界
  3. 自動釋放池是一個的結(jié)構(gòu) 杯巨,而且這個是一個雙向鏈表(含有父節(jié)點(diǎn) 和 子節(jié)點(diǎn))
  4. 自動釋放池和線程有很大的關(guān)系

那么努酸,問題來了??

  1. 自動釋放池是什么時候創(chuàng)建的服爷?
  2. 對象是如何被加入到自動釋放池的?
  3. 哪些對象會被加入到自動釋放池获诈?

帶著這些問題仍源,我們進(jìn)一步看看自動釋放池底層原理

1.2.1 AutoreleasePoolPage

至此舔涎,我們知道了自動釋放池是一個的結(jié)構(gòu)笼踩,就是AutoreleasePoolPage。大致結(jié)構(gòu)分布如下:

//************宏定義************
#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(),//開始存儲的位置
                                objc_thread_self(),//傳的是當(dāng)前線程亡嫌,當(dāng)前線程時通過tls獲取的
                                newParent,
                                newParent ? 1+newParent->depth : 0,//如果是第一頁深度為0嚎于,往后是前一個的深度+1
                                newParent ? newParent->hiwat : 0)
    {...}
    
    //析構(gòu)函數(shù)
    ~AutoreleasePoolPage() {...}
    
    ...
    
    //頁的開始位置
    id * begin() {...}
    
    //頁的結(jié)束位置
    id * end() {...}
   
    //頁是否為空
    bool empty() {...}
    
    //頁是否滿了
    bool full() {...}
   
    //頁的存儲是否少于一半
    bool lessThanHalfFull() {...}
     
     //添加釋放對象
    id *add(id obj){...}
    
    //釋放所有對象
    void releaseAll() {...}
    
    //釋放到stop位置之前的所有對象
    void releaseUntil(id *stop) {...}
    
    //殺掉
    void kill() {...}
    
    //釋放本地線程存儲空間
    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)頁滿的時候調(diào)用這個方法
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {...}
    
    //添加自動釋放對象挟冠,當(dāng)沒頁的時候使用這個方法
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj){...}
   
   //創(chuàng)建新頁
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj) {...}
    
public:
    //自動釋放
    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(){...}

它的父類是AutoreleasePoolPageData??

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    //用來校驗(yàn)AutoreleasePoolPage的結(jié)構(gòu)是否完整
    magic_t const magic;//16個字節(jié)
    //指向最新添加的autoreleased對象的下一個位置于购,初始化時指向begin()
    __unsafe_unretained id *next;//8字節(jié)
    //指向當(dāng)前線程
    pthread_t const thread;//8字節(jié)
    //指向父節(jié)點(diǎn),第一個結(jié)點(diǎn)的parent值為nil
    AutoreleasePoolPage * const parent;//8字節(jié)
    //指向子節(jié)點(diǎn)知染,最后一個結(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é)

    //默認(rèn)構(gòu)造函數(shù)
    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)
    {
    }
};

這里發(fā)現(xiàn)控淡, AutoreleasePoolPage同時也是一個雙向鏈表結(jié)構(gòu) --> 包含parentchild嫌吠。再看看AutoreleasePoolPageData結(jié)構(gòu)體所占內(nèi)存大小,算出來是56字節(jié)掺炭。

1.2.2 objc_autoreleasePoolPush

接下來我們看看壓棧操作objc_autoreleasePoolPush的源碼??

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

還是回到類AutoreleasePoolPagepush函數(shù)??

    static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

流程不難辫诅,大致分為以下幾步:

  1. 判斷當(dāng)前是否有池,
  2. 若沒有竹伸,則創(chuàng)建 -->autoreleaseNewPage
  3. 有池泥栖,則加入一個邊界對象POOL_BOUNDARY
  4. 返回池的邊界大小
autoreleaseNewPage

接下來我們仔細(xì)看看創(chuàng)建池的底層流程??

    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }

根據(jù)hotPage()簇宽,生成autoreleaseFullPage,否則就返回autoreleaseNoPage吧享。

hotPage

繼續(xù)魏割,我們先看看hotPage()源碼??

    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }

然后看看tls_get_direct ??

static inline void *tls_get_direct(tls_key_t k)
{ 
    ASSERT(is_valid_direct_key(k));

    if (_pthread_has_direct_tsd()) {
        return _pthread_getspecific_direct(k);
    } else {
        return pthread_getspecific(k);
    }
}

源碼可知,是在當(dāng)前線程pthead中钢颂,根據(jù)key獲取的頁AutoreleasePoolPage护蝶,其中key就是自動釋放池key??

static pthread_key_t const key = AUTORELEASE_POOL_KEY;

由此可見左电,當(dāng)前線程pthead指向的空間中缔逛,有一個類似字典結(jié)構(gòu)数冬,從這里根據(jù)AUTORELEASE_POOL_KEY能獲取到自動釋放池。

autoreleaseFullPage

接下來操灿,看看autoreleaseFullPage源碼??

    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 {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

然后add的源碼

    id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

流程也很簡單锯仪,核心代碼是do-while循環(huán):尋找當(dāng)前池頁的子頁,沒有則創(chuàng)建新的page趾盐,直到達(dá)到Size容量庶喜,然后將這個子頁設(shè)置為hotPage,即線程pThread中的 AUTORELEASE_POOL_KEY所對應(yīng)的value是這個子頁page救鲤,最后將壓棧的對象objcadd壓棧到這個子頁page中久窟,實(shí)際是對當(dāng)前page的next++

autoreleaseNoPage

objc_autoreleasePoolPush中本缠,如果當(dāng)前線程pThread中的AUTORELEASE_POOL_KEY所對應(yīng)的value沒有值時斥扛,則會進(jìn)入autoreleaseNoPage流程??

其中,我們發(fā)現(xiàn)丹锹,autoreleaseNoPage中會調(diào)用AutoreleasePoolPage的構(gòu)造函數(shù)稀颁,創(chuàng)建一個新的,構(gòu)造函數(shù)源碼如下??

    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),
                                objc_thread_self(),
                                newParent,
                                newParent ? 1+newParent->depth : 0,
                                newParent ? newParent->hiwat : 0)
    { 
        if (parent) {
            parent->check();
            ASSERT(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
    }

其中楣黍,newParent的值是nil峻村,而父類結(jié)構(gòu)體的構(gòu)造方法??

    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)
    {
    }

那么,_next就是begin()壓棧開始位置锡凝,_thread就是當(dāng)前線程objc_thread_self()

id * begin() {        
//等于 首地址+56(AutoreleasePoolPage類所占內(nèi)存大泄柑洹)
   return (id *) ((uint8_t *)this+sizeof(*this));
}

__attribute__((const))
static inline pthread_t objc_thread_self()
{
    //通過tls獲取當(dāng)前線程
    return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
autoreleaseFast

以上分析了autoreleaseNewPage窜锯,接下來就是else情況autoreleaseFast了,源碼??

    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

分析完autoreleaseFullPageautoreleaseNoPage之后芭析,現(xiàn)在回頭來看autoreleaseFast就很簡單了锚扎,大致流程??:

  1. 獲取當(dāng)前線程中AUTORELEASE_POOL_KEY對應(yīng)的
  2. 如果該存在,且有空間馁启,則壓棧objc
  3. 如果沒有空間驾孔,則擴(kuò)容芍秆,加一頁,再壓棧objc
  4. 如果該不存在翠勉,則創(chuàng)建一頁妖啥,其key設(shè)為AUTORELEASE_POOL_KEY,最后壓棧objc

小結(jié)
綜上所述对碌,objc_autoreleasePoolPush壓棧的核心流程如下??

  1. 當(dāng)沒有pool荆虱,即只有空占位符(存儲在線程tls中)時,則創(chuàng)建頁朽们,壓棧邊界對象
  2. 中壓棧普通對象怀读,通過next++進(jìn)行Page容量的標(biāo)記
  3. 容量滿了,就去子節(jié)點(diǎn)頁壓棧
  4. 如果當(dāng)前池中所有都滿了骑脱,那么新創(chuàng)建一個菜枷,壓棧邊界對象,再壓棧對象obj
附:打印查看AutoreleasePool的內(nèi)存結(jié)構(gòu)

在ARC下叁丧,是無法手動調(diào)用autorelease的啤誊,所以將Demo切換至MRC模式,修改工程配置BuildSetting歹袁,搜索automatic refer改為NO坷衍,如下圖??

再看示例代碼??

//************打印自動釋放池結(jié)構(gòu)************
extern void _objc_autoreleasePoolPrint(void);

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

運(yùn)行??

上圖紅框處就是自動釋放池的打印信息条舔,其中POOL就是池的邊界枫耳。再看池的首地址0x7f9ad8809000邊界地址0x7f9ad8809038,相差38(16進(jìn)制)孟抗,轉(zhuǎn)換成十進(jìn)制56迁杨,正好驗(yàn)證了之前說的AutoreleasePool類所占內(nèi)存空間的大小。

接著凄硼,我們將for循環(huán)改為505次铅协,run??

可以看到,第2頁存儲了一個摊沉,那么第一頁存儲了504個對象+1個邊界對象狐史,共計505個。不信说墨,再將循環(huán)次數(shù)改為505+505骏全,run??

第3頁是1個,共計是505+505+1個邊界對象尼斧,那么一頁最多可以存儲505個對象姜贡。

以上,我們可以得出以下結(jié)論:

  1. 邊界對象在第1頁棺棵,第1頁最多可存儲504個對象楼咳,第1頁滿了熄捍,會開辟新的一頁
  2. 從第2頁開始,最多可存儲505個對象
  3. 一頁中所有對象所占內(nèi)存是:505 * 8字節(jié)(因?yàn)槭侵羔槪?= 4040字節(jié)

再回頭看看類AutoreleasePool母怜,內(nèi)部定義了一個SIZE??


其中

#define PAGE_MIN_SHIFT          12
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)

1<<12就是2的12次方=4096余耽,之前說過,邊界對象距離池首地址相差56糙申,4040+56 = 4096宾添,完美契合!

AutoreleasePool的內(nèi)部結(jié)構(gòu)圖如下??

附面試題:

邊界對象在一個自動釋放池有幾個柜裸?

只有一個邊界對象對象缕陕,且在第一頁
第一頁最多可以存504個對象,第二頁開始最多存 505個

autorelease底層分析

MRC中疙挺,調(diào)用[obj autorelease]來延遲內(nèi)存的釋放是一件簡單自然的事扛邑,ARC下,我們甚至可以完全不知道Autorelease就能管理好內(nèi)存铐然。而在這背后蔬崩,objc和編譯器都幫我們做了哪些事呢,它們是如何協(xié)作來正確管理內(nèi)存的呢搀暑?
我們還是一樣找入口沥阳。打斷點(diǎn),看匯編??

定位到objc_autorelease自点,查看源碼??

__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

obj為nil或?yàn)?code>isTaggedPointer小對象時桐罕,直接返回obj,否則進(jìn)入autorelease()??

inline id 
objc_object::autorelease()
{
    ASSERT(!isTaggedPointer());
    if (fastpath(!ISA()->hasCustomRR())) {
        return rootAutorelease();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}

其中桂敛,ISA()->hasCustomRR()源碼??

bool hasCustomRR() const {
    return !bits.getBit(FAST_HAS_DEFAULT_RR);
}
// 其中FAST_HAS_DEFAULT_RR??
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<2)

那么autorelease()流程如下:

  1. ISA()->hasCustomRR() --> 判斷當(dāng)前類或者父類含有默認(rèn)的retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference方法功炮。
  2. 有,則直接消息發(fā)送术唬,調(diào)用autorelease方法
  3. 沒有薪伏,則調(diào)用rootAutorelease(),源碼??
inline id 
objc_object::rootAutorelease()
{
    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());
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

以上粗仓,小對象一律不看嫁怀,最終來到了autorelease(id obj),且是調(diào)用autoreleaseFast壓棧obj借浊。

注意眶掌,從ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);這句代碼可以看出,dest既可以是邊界對象巴碗,也可以是普通對象,所以autorelease沒有區(qū)分邊界對象的即寒。

1.2.3 objc_autoreleasePoolPop

那么橡淆,接下來召噩,我們最后來看看出棧objc_autoreleasePoolPop的底層流程??

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

和push一樣,進(jìn)入到類AutoreleasePoolPage的pop函數(shù)??

接著逸爵,我們看看popPage??

releaseUntil

我們先看看重點(diǎn)函數(shù)releaseUntil??

上圖可知具滴,外層while循環(huán),其實(shí)是在遍歷你要釋放的obj之前的對象师倔,然后逐一進(jìn)行釋放构韵,同時將其對應(yīng)的內(nèi)存狀態(tài)標(biāo)識為SCRIBBLE(釋放后的狀態(tài))。

kill

最后看看kill??

小結(jié)
綜上所述趋艘,objc_autoreleasePoolPop出棧的核心流程如下??

  1. releaseUntil,釋放當(dāng)前頁出棧對象obj+obj之前的對象疲恢,之前的頁
  2. 也是通過next--標(biāo)記當(dāng)前頁的容量
  3. 出棧后,判斷當(dāng)前頁的容量是否少于505/2(一半容量)瓷胧,則刪除子節(jié)點(diǎn)頁面显拳,不是則刪除孫子節(jié)點(diǎn)頁面

二、RunLoop

RunLoop也是近年來面試必問的知識點(diǎn)搓萧,而且基本涉及很細(xì)很底層杂数,所以本篇也在此探索一些底層實(shí)現(xiàn)原理。我們對于RunLoop瘸洛,最關(guān)心的幾個問題無非是以下:

  1. runloop是什么揍移?
  2. runloop和線程的關(guān)系?
  3. runloop是什么時候創(chuàng)建的反肋?

2.1 RunLoop簡介

RunLoop事件接收和分發(fā)機(jī)制的一個實(shí)現(xiàn)那伐,是線程相關(guān)的基礎(chǔ)框架的一部分,一個RunLoop就是一個事件處理的循環(huán)囚玫,用來不停的調(diào)度工作以及處理輸入事件喧锦。RunLoop本質(zhì)是一個do-while循環(huán),沒事做就休息抓督,來活了就干活燃少。與普通的while循環(huán)的區(qū)別??

  • 普通的while循環(huán)會導(dǎo)致CPU進(jìn)入忙等待狀態(tài),即一直消耗cpu
  • RunLoop則不會铃在,RunLoop是一種閑等待阵具,即它具備休眠功能
RunLoop的作用
  1. 保持程序的持續(xù)運(yùn)行
  2. 處理App中的各種事件(觸摸、定時器定铜、performSelector)
  3. 節(jié)省cpu資源阳液,提供程序的性能,該做事就做事揣炕,該休息就休息
Runloop源碼

RunLoop源碼的下載地址帘皿,在其中找到最新版下載即可。

2.2 RunLoop與線程的關(guān)系

RunLoop與線程的關(guān)系得從RunLoop的獲取方式這個切入點(diǎn)查看畸陡。系統(tǒng)給了兩種方式獲取RunLoop??

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

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

// -----------------------------分割線 -----------------------------

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

接著看看_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是一一對應(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) {
        //如果沒有獲取到墓陈,則新建一個運(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只有兩種:一種是主線程的, 一個是其他線程的贡必。即runloop和線程是一一對應(yīng)的兔港。

2.3 RunLoop的使用

_CFRunLoopGet0的流程中,有一個創(chuàng)建步驟__CFRunLoopCreate赊级,我們先看看其源碼押框。

2.3.1 創(chuàng)建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;
}

這個創(chuàng)建理逊,和我們平時寫的初始化代碼基本一樣橡伞,我們再看看返回的對象是CFRunLoopRef類型??

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;

接著看__CFRunLoop??

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

從結(jié)構(gòu)體__CFRunLoop中可以得出,一個RunLoop依賴于多個Mode(_commonModes)晋被,意味著一個RunLoop需要處理多個事務(wù)_commonModeItems兑徘,那么一個Mode對應(yīng)多個Item,而一個item中羡洛,包含了timer挂脑、source、observer欲侮,如下所示??

Mode類型

其中mode在蘋果文檔中提及的有五個崭闲,而在iOS中公開暴露出來的只有 NSDefaultRunLoopModeNSRunLoopCommonModes。 而NSRunLoopCommonModes實(shí)際上是一個 Mode 的集合威蕉,默認(rèn)包括 NSDefaultRunLoopModeNSEventTrackingRunLoopMode刁俭。

  1. NSDefaultRunLoopMode:默認(rèn)的mode,正常情況下都是在這個mode
  2. NSConnectionReplyMode
  3. NSModalPanelRunLoopMode
  4. NSEventTrackingRunLoopMode:使用這個Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動)
  5. NSRunLoopCommonModes:偽模式韧涨,靈活性更好
Source & Timer & Observer

Source表示可以喚醒RunLoop的一些事件牍戚,例如用戶點(diǎn)擊了屏幕,就會創(chuàng)建一個RunLoop虑粥,主要分為Source0和Source1:

  • Source0 表示 非系統(tǒng)事件如孝,即用戶自定義的事
  • Source1 表示系統(tǒng)事件,主要負(fù)責(zé)底層的通訊娩贷,具備喚醒能力

Timer 就是常用NSTimer定時器這一類

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是一對多關(guān)系

通過lldb命令獲取mainRunloop、currentRunloop的currentMode

由此可見但荤,runloop在運(yùn)行時的mode只有一個罗岖。

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

由此可見腹躁,runloopCFRunloopMode一對多的關(guān)系

示例驗(yàn)證:mode和Item也是一對多關(guān)系

在RunLoop源碼中查看Item類型南蓬,有以下幾種??

  1. block應(yīng)用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  2. 調(diào)用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  3. 響應(yīng)source0: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  4. 響應(yīng)source1: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  5. GCD主隊列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  6. observer源: __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

在這里以Timer為例纺非,一般初始化timer時,都會將timer通過addTimer:forMode:方法添加到Runloop中赘方,于是在源碼中查找addTimer的相關(guān)方法烧颖,即CFRunLoopAddTimer方法,其源碼實(shí)現(xiàn)如下

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 是一對多的窄陡, mode與item也是一對多的
        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);
}

其實(shí)現(xiàn)主要判斷kCFRunLoopCommonModes跳夭,然后查找runloop的mode進(jìn)行匹配處理:

  • 其中kCFRunLoopCommonModes不是一種模式涂圆,是一種抽象的偽模式,比defaultMode更加靈活币叹。
  • 通過CFSetAddValue(rl->_commonModeItems, rlt);可以得知润歉,runloop與mode 是一對多的,同時可以得出mode 與 item 也是一對多颈抚。

2.3.2 執(zhí)行RunLoop

RunLoop的執(zhí)行依賴于run方法踩衩,從下面的堆棧信息中可以看出,其底層執(zhí)行的是__CFRunLoopRun方法??


其源碼如下??

/* 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);
    
    ...
}

這是省略了其它模式的處理贩汉,只看timer的模式驱富,其實(shí)進(jìn)入__CFRunLoopRun源碼,針對不同的對象匹舞,有不同的處理

  • 如果有observer褐鸥,則調(diào)用 __CFRunLoopDoObservers
  • 如果有block,則調(diào)用__CFRunLoopDoBlocks
  • 如果有timer策菜,則調(diào)用 __CFRunLoopDoTimers
  • 如果是source0晶疼,則調(diào)用__CFRunLoopDoSources0
  • 如果是source1,則調(diào)用__CFRunLoopDoSource1

接著我們看看__CFRunLoopDoTimers??

static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    ...
    //循環(huán)遍歷又憨,做下層單個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;
    }
    ...
}

__CFRunLoopDoTimer的主要邏輯是timer執(zhí)行完畢后翠霍,會主動調(diào)用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__函數(shù)??

// 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堆棧調(diào)用中的一致??

timer流程總結(jié):

  1. 為自定義的timer,設(shè)置Mode蠢莺,并將其加入RunLoop
  2. 在RunLoop的run方法執(zhí)行時寒匙,會調(diào)用__CFRunLoopDoTimers執(zhí)行所有timer
  3. 在__CFRunLoopDoTimers方法中,會通過for循環(huán)執(zhí)行單個timer的操作
  4. 在__CFRunLoopDoTimer方法中,timer執(zhí)行完畢后锄弱,會執(zhí)行對應(yīng)的timer回調(diào)

以上考蕾,是針對timer的執(zhí)行分析,對于observer会宪、block肖卧、source0、source1掸鹅,其執(zhí)行原理與timer是類似的塞帐,這里就不再重復(fù)說明以下是蘋果官方文檔針對RunLoop處理不同源的圖示??

2.4 RunLoop底層原理(偽代碼)

之前看RunLoop調(diào)用棧信息,我們知道RunLoop底層是通過CFRunLoopRun??

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

果然巍沙,是個do-while循環(huán)葵姥,其中傳入的參數(shù)1.0e10(科學(xué)計數(shù)) 等于 1* e^10,用于表示超時時間句携。接著看看CFRunLoopRunSpecific??

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    //首先根據(jù)modeName找到對應(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ù)modeName找到對應(yīng)的mode類型削咆,然后主要分為三種情況:

  1. 如果是entry,則通知observer敞临,即將進(jìn)入runloop
  2. 如果是exit态辛,則通過observer,即將退出runloop
  3. 如果是其他中間狀態(tài)挺尿,主要是通過runloop處理各種源

接著看看核心流程__CFRunLoopRun奏黑,由于這部分代碼較多,于是這里用偽代碼代替编矾。其主要邏輯是根據(jù)不同的事件源進(jìn)行不同的處理熟史,當(dāng)RunLoop休眠時,可以通過相應(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開啟一個定時器窄俏,然后開始跑圈
    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;//超時
        } 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í)行流程分析完畢,可以看看下面的經(jīng)典圖片??

總結(jié)

本篇文章主要分析了兩大知識點(diǎn):AutoreleasePoolRunLoop仰坦,根據(jù)cpp和匯編查找入口履植,然后找到底層源碼分析它們的實(shí)現(xiàn)流程,按照這個思路悄晃,我們終于清楚了系統(tǒng)是如何處理自動釋放玫霎,RunLoop是如何處理各種事件源信息。相信大家在面試時碰到這些問題,就胸有成竹庶近,應(yīng)對自如了翁脆,哈哈!

參考:
iOS-底層原理 33:內(nèi)存管理(三)AutoReleasePool & NSRunLoop 底層分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鼻种,一起剝皮案震驚了整個濱河市反番,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叉钥,老刑警劉巖恬口,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沼侣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)歉秫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蛾洛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人雁芙,你說我怎么就攤上這事轧膘。” “怎么了兔甘?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵谎碍,是天一觀的道長。 經(jīng)常有香客問我洞焙,道長蟆淀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任澡匪,我火速辦了婚禮熔任,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唁情。我一直安慰自己疑苔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布甸鸟。 她就那樣靜靜地躺著惦费,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抢韭。 梳的紋絲不亂的頭發(fā)上薪贫,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音篮绰,去河邊找鬼后雷。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的臀突。 我是一名探鬼主播勉抓,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼候学!你這毒婦竟也來了藕筋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤梳码,失蹤者是張志新(化名)和其女友劉穎隐圾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掰茶,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暇藏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了濒蒋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盐碱。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沪伙,靈堂內(nèi)的尸體忽然破棺而出瓮顽,到底是詐尸還是另有隱情,我是刑警寧澤围橡,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布暖混,位于F島的核電站,受9級特大地震影響翁授,放射性物質(zhì)發(fā)生泄漏拣播。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一黔漂、第九天 我趴在偏房一處隱蔽的房頂上張望诫尽。 院中可真熱鬧,春花似錦炬守、人聲如沸牧嫉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酣藻。三九已至,卻和暖如春鳍置,著一層夾襖步出監(jiān)牢的瞬間辽剧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工税产, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怕轿,地道東北人偷崩。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像撞羽,于是被迫代替她去往敵國和親阐斜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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