iOS AutoReleasePool 自動釋放池以及RunLoop分析

AutoReleasePool 自動釋放池

自動釋放池 -> 內(nèi)存自動回收機制 -> 變量release的時機延遲
對象 -> 正常情況下,變量會在超出其作用域的時立即release -> 加入到了自動釋放池中 -> 不會立即釋放,會等到runloop休眠/超出autoreleasepool作用域{}之后才會被釋放

  1. 從程序啟動到加載完成,主線程對應(yīng)的runloop會處于休眠狀態(tài),等待用戶交互來喚醒runloop
  2. 用戶的每一次交互都會啟動一次runloop,用于處理用戶的所有點擊、觸摸事件等
  3. runloop在監(jiān)聽到交互事件后,就會創(chuàng)建自動釋放池,并將所有延遲釋放的對象添加到自動釋放池中
  4. 在一次完整的runloop結(jié)束之前,會向自動釋放池中所有對象發(fā)送release消息,然后銷毀自動釋放池
Clang分析AutoReleasePool
  • __AtAutoreleasePool是一個結(jié)構(gòu)體,有構(gòu)造函數(shù) + 析構(gòu)函數(shù),結(jié)構(gòu)體定義的對象在作用域結(jié)束后,會自動調(diào)用析構(gòu)函數(shù)
  • 其中{} 是 作用域 ,優(yōu)點是結(jié)構(gòu)清晰,可讀性強,可以及時創(chuàng)建銷毀
//構(gòu)造函數(shù) + 析構(gòu)函數(shù)舉例說明
struct CJLTest{  
    CJLTest  
(){  
        printf("1123 - %s\n", __func__);  
    }  
    ~CJLTest(){  
        printf("5667 - %s\n", __func__);  
    }  
};  

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

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

CJLTest創(chuàng)建對象時,會自動調(diào)用構(gòu)造函數(shù),在出了{}作用域后,會自動調(diào)用析構(gòu)函數(shù)

匯編分析
  • autoreleasepool -> 結(jié)構(gòu)體對象,一個自動釋放池對象就是頁 -> 棧結(jié)構(gòu)存儲,符合先進后出的原則即可
  • 頁的棧底是一個56字節(jié)大小的空占位符,一頁總大小為4096字節(jié)
  • 只有第一頁有哨兵對象,最多存儲504個對象,從第二頁開始最多存儲505個對象
  • autoreleasepool在加入要釋放的對象時 -> 底層調(diào)用的是objc_autoreleasePoolPush方法
  • autoreleasepool在調(diào)用析構(gòu)函數(shù)釋放時 -> 內(nèi)部的實現(xiàn)是調(diào)用objc_autoreleasePoolPop方法
底層分析
Autorelease pool implementation  

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

- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.  
每個指針都是要釋放的對象,或者是POOL_BOUNDARY,它是自動釋放池的邊界。  

- 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的指針。彈出池后,將釋放比哨點更熱的每個對象。  

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

- Thread-local storage points to the hot page, where newly autoreleased objects are stored.   
線程本地存儲指向熱頁面,該頁面存儲新自動釋放的對象。  

對以上的解釋說明:

  1. 自動釋放池 是一個 關(guān)于指針的棧結(jié)構(gòu)
  2. 指針是指要釋放的對象或者 pool_boundary 哨兵(現(xiàn)在經(jīng)常被稱為 邊界)
  3. 自動釋放池是一個頁的結(jié)構(gòu)(虛擬內(nèi)存中提及過) ,而且這個頁是一個雙向鏈表(表示有父節(jié)點 和 子節(jié)點,在類中提及過,即類的繼承鏈)
  4. 自動釋放池和線程有關(guān)系

需要注意的地方:

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

AutoreleasePoolPage

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

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

源碼 -> 調(diào)用的AutoreleasePoolPage的push和pop實現(xiàn),以下是其定義,從定義中可以看出,自動釋放池是一個,同時也是一個對象,這個頁的大小是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(),//開始存儲的位置  
                                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(){...}  
  • AutoreleasePoolPage -> 繼承自AutoreleasePoolPageData,且該類的屬性也是來自父類,以下是AutoreleasePoolPageData的定義,
  • AutoreleasePoolPage對象,所以有以下一個關(guān)系鏈AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage,從這里可以說明自動釋放池 -> 頁 -> 雙向鏈表結(jié)構(gòu)
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    //用來校驗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é)點,第一個結(jié)點的parent值為nil
    AutoreleasePoolPage * const parent;//8字節(jié)
    //指向子節(jié)點,最后一個結(jié)點的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(對象)、parent(對象)、child(對象)均占8字節(jié)(即4*8=32字節(jié))
  • 屬性depth、hiwat類型為uint32_t,實際類型是unsigned int類型,均占4字節(jié)(即2*4=8字節(jié))

objc_autoreleasePoolPush 源碼分析

  1. 判斷是否為有 pool
  2. 如果沒有,則通過autoreleaseNewPage方法創(chuàng)建
  3. 如果有,則通過autoreleaseFast壓棧哨兵對象
//入棧
static inline void *push() 
{
    id *dest;
    //判斷是否有pool
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.自動釋放池從新池頁面開始
        //如果沒有,則創(chuàng)建
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        //壓棧一個POOL_BOUNDARY,即壓棧哨兵
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
1. 創(chuàng)建頁 autoreleaseNewPage

objc_autoreleasePoolPush -> push -> autoreleaseNewPage

  1. 通過hotPage`獲取當(dāng)前頁,判斷當(dāng)前頁是否存在
  2. 如果存在,則通過autoreleaseFullPage方法壓棧對象
  3. 如果不存在,則通過autoreleaseNoPage方法創(chuàng)建頁
//創(chuàng)建新頁
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
    //獲取當(dāng)前操作頁
    AutoreleasePoolPage *page = hotPage();
    //如果存在,則壓棧對象
    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);
    //如果是一個空池,則返回nil,否則,返回當(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)識符置為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;
    }
    //如果對象不是哨兵對象,且沒有Pool,則報錯
    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;
    }
    //如果對象是哨兵對象,且沒有申請自動釋放池內(nèi)存,則設(shè)置一個空占位符存儲在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)識符為YES,則壓棧哨兵對象
    if (pushExtraBoundary) {
        //壓棧哨兵
        page->add(POOL_BOUNDARY);
    }
    
    // Push the requested object or pool.
    //壓棧對象
    return page->add(obj);
}

autoreleaseNoPage -> 當(dāng)前線程的自動釋放池是通過AutoreleasePoolPage創(chuàng)建的 -> 構(gòu)造方法 -> 實現(xiàn)是通過父類AutoreleasePoolPageData的初始化方法

//**********AutoreleasePoolPage構(gòu)造方法**********
    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)
{ 
    if (parent) {
        parent->check();
        ASSERT(!parent->child);
        parent->unprotect();
        //this 表示 新建頁面,將當(dāng)前頁面的子節(jié)點 賦值為新建頁面
        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)
    {
    }
  • begin()表示壓棧的位置(即下一個要釋放對象的壓棧地址) -> 源碼調(diào)試begin,發(fā)現(xiàn)其具體實現(xiàn)等于頁首地址+56,其中的56就是結(jié)構(gòu)體AutoreleasePoolPageData的內(nèi)存大小
//********begin()********
//頁的開始位置
id * begin() {
    //等于 首地址+56(AutoreleasePoolPage類所占內(nèi)存大?。?    return (id *) ((uint8_t *)this+sizeof(*this));
}
  • objc_thread_self() 表示的是當(dāng)前線程,而當(dāng)前線程時通過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é)點
  • 后續(xù)兩個參數(shù)是通過父節(jié)點的深度、最大入棧個數(shù)計算depth以及hiwat
查看自動釋放池內(nèi)存結(jié)構(gòu)

ARC模式下 -> 無法手動調(diào)用autorelease -> 切換至MRC模式(Build Settings -> Objectice-C Automatic Reference Counting設(shè)置為NO)

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

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

//6個 -> 壓棧的對象其實只有5個,其中的POOL表示哨兵,即邊界,其目的是為了防止越界

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

結(jié)論
  1. 第一頁可以存放504個對象,且只有第一頁有哨兵,當(dāng)一頁壓棧滿了,就會開辟新的一頁
  2. 第二頁開始,最多可以存放505個對象
  3. 一頁的大小等于 505 * 8 = 4040

這個結(jié)論同樣可以通過AutoreleasePoolPage中的SIZE來得到印證,從其定義中我們可以得出,一頁的大小是4096字節(jié),而在其構(gòu)造函數(shù)中對象的壓棧位置,是從首地址+56開始的,所以可以一頁中實際可以存儲4096-56 = 4040字節(jié),轉(zhuǎn)換成對象是4040 / 8 = 505個,即一頁最多可以存儲505個對象,其中第一頁有哨兵對象只能存儲504個

問題: 哨兵在一個自動釋放池有幾個?
  • 只有一個哨兵對象,且哨兵在第一頁
  • 第一頁最多可以存504個對象,第二頁開始最多存 505個
壓棧對象 autoreleaseFast
  • 獲取當(dāng)前操作頁,并判斷頁是否存在以及是否滿了
  • 如果頁存在,且未滿,則通過add方法壓棧對象
  • 如果頁存在,且滿了,則通過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 方法

用于判斷當(dāng)前頁是否已經(jīng)存儲滿了,如果當(dāng)前頁已經(jīng)滿了,通過do-while循環(huán)查找子節(jié)點對應(yīng)的頁,如果不存在,則新建頁,并壓棧對象

//添加自動釋放對象,當(dāng)頁滿的時候調(diào)用這個方法
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);
    //對象壓棧
    return page->add(obj);
}

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

add 方法

主要是添加釋放對象,其底層是實現(xiàn)是通過next指針存儲釋放對象,并將next指針遞增,表示下一個釋放對象存儲的位置。從這里可以看出頁是通過棧結(jié)構(gòu)存儲

//添加釋放對象
id *add(id obj)
{
    ASSERT(!full());
    unprotect();
    //傳入對象存儲的位置
    id *ret = next;  // faster than `return next-1` because of aliasing
    //將obj壓棧到next指針位置,然后next進行++,即下一個對象存儲的位置
    *next++ = obj;
    protect();
    return ret;
}
autorelease 底層分析
  • 如果不是對象 或者 是小對象,則直接返回
  • 如果是對象,則調(diào)用對象的autorelease進行釋放
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
    //如果不是對象,則直接返回
    if (!obj) return obj;
    //如果是小對象,也直接返回
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

無論是壓棧哨兵對象,還是普通對象,都會來到autoreleaseFast方法,只是區(qū)別標(biāo)識不同而以

objc_autoreleasePoolPop 源碼分析

objc_autoreleasePoolPop方法中有個參數(shù),在clang分析時,發(fā)現(xiàn)傳入的參數(shù)是push壓棧后返回的哨兵對象,即ctxt,其目的是避免出?;靵y,防止將別的對象出棧

  • pop源碼實現(xiàn)

    • 空頁面的處理,并根據(jù)token獲取page
    • 容錯處理
    • 通過popPage出棧頁
  • 進入popPage源碼,其中傳入的allowDebug為false,則通過releaseUntil出棧當(dāng)前頁stop位置之前的所有對象,即向棧中的對象發(fā)送release消息,直到遇到傳入的哨兵對象

  • 進入releaseUntil實現(xiàn),主要是通過循環(huán)遍歷,判斷對象是否等于stop,其目的是釋放stop之前的所有的對象

    • 首先通過獲取page的next釋放對象(即page的最后一個對象),并對next進行遞減,獲取上一個對象
    • 判斷是否是哨兵對象,如果不是則自動調(diào)用objc_release釋放
  • 進入kill實現(xiàn),主要是銷毀當(dāng)前頁,將當(dāng)前頁賦值為父節(jié)點頁,并將父節(jié)點頁的child對象指針置為nil

總結(jié)
  • 在自動釋放池的壓棧(即push)操作中

    • 當(dāng)沒有pool,即只有空占位符(存儲在tls中)時,則創(chuàng)建頁,壓棧哨兵對象
    • 在頁中壓棧普通對象主要是通過next指針遞增進行的
    • 當(dāng)頁滿了時,需要設(shè)置頁的child對象為新建頁
  • 在自動釋放池的出棧(即pop)操作中

    • 在頁中出棧普通對象主要是通過next指針遞減進行的
    • 當(dāng)頁空了時,需要賦值頁的parent對象為當(dāng)前頁

RunLoop

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

1. RunLoop介紹

runloop -> 本質(zhì)是一個do-while循環(huán) -> 與普通的while循環(huán)有區(qū)別 -> 普通的while循環(huán)會讓CPU忙等(一直消耗CPU) -> 而runloop不會 -> 閑等待 -> 具備休眠功能

runloop的作用
  • 保持程序的持續(xù)運行
  • 處理App中的各種事件(觸摸、定時器、performSelector)
  • 節(jié)省cpu資源,提供程序的性能,該做事就做事,該休息就休息
runloop與線程的關(guān)系
//RunLoop獲取
// 主運行循環(huán)
 CFRunLoopRef mainRunloop = CFRunLoopGetMain();
 // 當(dāng)前運行循環(huán)
 CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
// 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)建主運行循環(huán)
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //利用dict,進行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) {
        //如果沒有獲取到,則新建一個運行循環(huán)
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //將新建的runloop 與 線程進行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)的

RunLoop的創(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;
}

CFRunLoopRef的定義 -> RunLoop也是一個對象 -> __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;
};

一個RunLoop依賴于多個Mode -> 一個RunLoop需要處理多個事務(wù) -> 一個Mode對應(yīng)多個Item -> 一個item中,包含了timer、source、observer

Mode類型
  • NSDefaultRunLoopMode -> 默認(rèn)的mode,正常情況下都是在這個mode
  • NSConnectionReplyMode
  • NSModalPanelRunLoopMode
  • NSEventTrackingRunLoopMode -> 使用這個Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動)
  • NSRunLoopCommonModes -> 偽模式,靈活性更好
Source & Time & Observer
  • Source -> 可以喚醒RunLoop的一些事件,例如用戶點擊了屏幕,就會創(chuàng)建一個RunLoop,主要分為Source0和Source1
    • Source0 -> 用戶自定義的事件
    • Source1 -> 表示系統(tǒng)事件,主要負(fù)責(zé)底層的通訊,具備喚醒能力
  • Timer -> NSTimer
  • Observer -> 監(jiān)聽RunLoop的狀態(tài)變化,并作出一定響應(yīng)
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    //進入RunLoop
    kCFRunLoopEntry = (1UL << 0),
    //即將處理Timers
    kCFRunLoopBeforeTimers = (1UL << 1),
    //即將處理Source
    kCFRunLoopBeforeSources = (1UL << 2),
    //即將進入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),
    //被喚醒
    kCFRunLoopAfterWaiting = (1UL << 6),
    //退出RunLoop
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
model & Item
  • runloop 和 CFRunloopMode 具有 一對多的關(guān)系

  • 在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主隊列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
    • observer源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
  • 以Timer為例 -> 初始化timer -> addTimer:forMode:方法添加到Runloop中 -> 在源碼中查找addTimer的相關(guān)方法 -> CFRunLoopAddTimer方法 -> 其實現(xiàn)主要判斷是否是kCFRunLoopCommonModes,然后查找runloop的mode進行匹配處理

    • kCFRunLoopCommonModes 不是一種模式,是一種抽象的偽模式,比defaultMode更加靈活
    • 通過CFSetAddValue(rl->_commonModeItems, rlt);可以得知,runloop與mode 是一對多的,同時可以得出mode 與 item 也是一對多的
RunLoop執(zhí)行

RunLoop -> run方法 -> 底層執(zhí)行的是__CFRunLoopRun方法

  • 進入__CFRunLoopRun源碼,針對不同的對象,有不同的處理

    • 如果有observer,則調(diào)用 __CFRunLoopDoObservers
    • 如果有block,則調(diào)用__CFRunLoopDoBlocks
    • 如果有timer,則調(diào)用 __CFRunLoopDoTimers
    • 如果是source0,則調(diào)用__CFRunLoopDoSources0
    • 如果是source1,則調(diào)用__CFRunLoopDoSource1
  • 進入__CFRunLoopDoTimers源碼,主要是通過for循環(huán),對單個timer進行處理

  • 進入__CFRunLoopDoTimer源碼,主要邏輯是timer執(zhí)行完畢后,會主動調(diào)用CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION函數(shù),正好與timer堆棧調(diào)用中的一致

timer執(zhí)行總結(jié)
  • 為自定義的timer,設(shè)置Mode,并將其加入RunLoop中
  • 在RunLoop的run方法執(zhí)行時,會調(diào)用__CFRunLoopDoTimers執(zhí)行所有timer
  • 在__CFRunLoopDoTimers方法中,會通過for循環(huán)執(zhí)行單個timer的操作
  • 在__CFRunLoopDoTimer方法中,timer執(zhí)行完畢后,會執(zhí)行對應(yīng)的timer回調(diào)函數(shù)
RunLoop底層原理

run在底層的實現(xiàn)路徑為 CFRunLoopRun -> CFRunLoopRun -> __CFRunLoopRun

  • 進入CFRunLoopRun源碼,其中傳入的參數(shù)1.0e10(科學(xué)計數(shù)) 等于 1* e^10,用于表示超時時間
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);
}
  • 進入CFRunLoopRunSpecific源碼,,首先根據(jù)modeName找到對應(yīng)的mode,然后主要分為三種情況
    • 如果是entry,則通知observer,即將進入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找到對應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    // 通知 Observers: RunLoop 即將進入 loop。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // 內(nèi)部函數(shù),進入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // 通知 Observers: RunLoop 即將退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
    
}
  • 進入入__CFRunLoopRun源碼,由于這部分代碼較多,于是這里用偽代碼代替。其主要邏輯是根據(jù)不同的事件源進行不同的處理,當(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: 即將進入休眠
        __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;
}
問題
1. 臨時變量什么時候釋放?
  • 如果在正常情況下,一般是超出其作用域就會立即釋放
  • 如果將臨時變量加入了自動釋放池,會延遲釋放,即在runloop休眠或者autoreleasepool作用域之后釋放
2. AutoreleasePool原理
  • 自動釋放池的本質(zhì)是一個AutoreleasePoolPage結(jié)構(gòu)體對象,是一個棧結(jié)構(gòu)存儲的頁,每一個AutoreleasePoolPage都是以雙向鏈表的形式連接
  • 自動釋放池的壓棧和出棧主要是通過結(jié)構(gòu)體的構(gòu)造函數(shù)和析構(gòu)函數(shù)調(diào)用底層的objc_autoreleasePoolPushobjc_autoreleasePoolPop,實際上是調(diào)用AutoreleasePoolPage的push和pop兩個方法
  • 每次調(diào)用push操作其實就是創(chuàng)建一個新的AutoreleasePoolPage,而AutoreleasePoolPage的具體操作就是插入一個POOL_BOUNDARY,并返回插入POOL_BOUNDARY的內(nèi)存地址。而push內(nèi)部調(diào)用autoreleaseFast方法處理,主要有以下三種情況
    • 當(dāng)page存在,且不滿時,調(diào)用add方法將對象添加至page的next指針處,并next遞增
    • 當(dāng)page存在,且已滿時,調(diào)用autoreleaseFullPage初始化一個新的page,然后調(diào)用add方法將對象添加至page棧中
    • 當(dāng)page不存在時,調(diào)用autoreleaseNoPage創(chuàng)建一個hotPage,然后調(diào)用add方法將對象添加至page棧中
  • 當(dāng)執(zhí)行pop操作時,會傳入一個值,這個值就是push操作的返回值,即POOL_BOUNDARY的內(nèi)存地址token。所以pop內(nèi)部的實現(xiàn)就是根據(jù)token找到哨兵對象所處的page中,然后使用 objc_release 釋放 token之前的對象,并把next 指針到正確位置
3. AutoreleasePool能否嵌套使用?
  • 可以嵌套使用,其目的是可以控制應(yīng)用程序的內(nèi)存峰值,使其不要太高
  • 可以嵌套的原因是因為自動釋放池是以棧為節(jié)點,通過雙向鏈表的形式連接的,且是和線程一一對應(yīng)的
  • 自動釋放池的多層嵌套其實就是不停的push哨兵對象,在pop時,會先釋放里面的,在釋放外面的
4. 哪些對象可以加入AutoreleasePool?alloc創(chuàng)建可以嗎?
  • 使用new、alloc、copy關(guān)鍵字生成的對象和retain了的對象需要手動釋放,不會被添加到自動釋放池中
  • 設(shè)置為autorelease的對象不需要手動釋放,會直接進入自動釋放池
  • 所有 autorelease 的對象,在出了作用域之后,會被自動添加到最近創(chuàng)建的自動釋放池中
5:AutoreleasePool的釋放時機是什么時候?
  • App 啟動后,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()。
  • 第一個 Observer 監(jiān)視的事件是 Entry(即將進入 Loop),其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其 order 是 -2147483647,優(yōu)先級最高,保證創(chuàng) 建釋放池發(fā)生在其他所有回調(diào)之前。
  • 第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進入休眠) 時調(diào)用 _objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即 將退出 Loop) 時調(diào)用 _objc_autoreleasePoolPop()來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優(yōu)先級最低,保證其釋放池子發(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.

大致意思如下:

  • 每個線程,包括主線程在內(nèi)都維護了自己的自動釋放池堆棧結(jié)構(gòu)
  • 新的自動釋放池在被創(chuàng)建時,會被添加到棧頂;當(dāng)自動釋放池銷毀時,會從棧中移除
  • 對于當(dāng)前線程來說,會將自動釋放的對象放入自動釋放池的棧頂;在線程停止時,會自動釋放掉與該線程關(guān)聯(lián)的所有自動釋放池
  • 總結(jié):每個線程都有與之關(guān)聯(lián)的自動釋放池堆棧結(jié)構(gòu),新的pool在創(chuàng)建時會被壓棧到棧頂,pool銷毀時,會被出棧,對于當(dāng)前線程來說,釋放對象會被壓棧到棧頂,線程停止時,會自動釋放與之關(guān)聯(lián)的自動釋放池
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)之前之前,會自動創(chuàng)建一個 autoreleasePool
  • 并且會在事件循環(huán)結(jié)束時,執(zhí)行drain操作,釋放其中的對象
RunLoop相關(guān)
當(dāng)前有個子線程,子線程中有個timer。timer是否能夠執(zhí)行 并進行持續(xù)的打印?
 CJLThread *thread = [[CJLThread alloc] initWithBlock:^{

        // thread.name = nil 因為這個變量只是捕捉
        // CJLThread *thread = nil
        // thread = 初始化 捕捉一個nil進來
        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];

不可以,因為子線程的runloop默認(rèn)不啟動, 需要runloop run啟動,需要將上述代碼改成下面這樣:

//改成
 CJLThread *thread = [[CJLThread alloc] initWithBlock:^{

    // thread.name = nil 因為這個變量只是捕捉
    // CJLThread *thread = nil
    // thread = 初始化 捕捉一個nil進來
    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)系
  • 每個線程都有一個與之對應(yīng)的RunLoop,所以RunLoop與線程是一一對應(yīng)的,其綁定關(guān)系通過一個全局的DIctionary存儲,線程為key,runloop為value。
  • 線程中的RunLoop主要是用來管理線程的,當(dāng)線程的RunLoop開啟后,會在執(zhí)行完任務(wù)后進行休眠狀態(tài),當(dāng)有事件觸發(fā)喚醒時,又開始工作,即有活時干活,沒活就休息
  • 主線程的RunLoop是默認(rèn)開啟的,在程序啟動之后,會一直運行,不會退出
  • 其他線程的RunLoop默認(rèn)是不開啟的,如果需要,則手動開啟
3:NSRunLoop 和 CFRunLoopRef 區(qū)別
  • NSRunLoop是基于CFRunLoopRef面向?qū)ο蟮腁PI,是不安全的
  • CFRunLoopRef是基于C語言,是線程安全的
4:Runloop的mode作用是什么?
  • mode主要是用于指定RunLoop中事件優(yōu)先級的
5:以+scheduledTimerWithTimeInterval:的方式觸發(fā)的timer,在滑動頁面上的列表時,timer會暫?;卣{(diào), 為什么?如何解決?
  • timer停止的原因是因為滑動scrollView時,主線程的RunLoop會從NSDefaultRunLoopMode切換到UITrackingRunLoopMode,而timer是添加在NSDefaultRunLoopMode。所以timer不會執(zhí)行
  • 將timer放入NSRunLoopCommonModes中執(zhí)行
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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