本文主要分析 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ī)制如下圖所示
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_autoreleasePoolPush
和objc_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)用的
AutoreleasePoolPage
的push
和pop
實(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)
- 發(fā)現(xiàn)其中有
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
)
- 定義如下代碼
//************打印自動(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
表示哨兵
,即邊界
靶病,其目的是為了防止越界
查看自動(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)圖示如下
面試題:哨兵在一個(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ì)象為新建頁
- 當(dāng)沒有pool靠柑,即只有空占位符(存儲(chǔ)在tls中)時(shí)寨辩,則創(chuàng)建頁,
所以,綜上所述隔嫡,autorelease
和objc_autoreleasePush
的整體底層的流程如下圖所示
- 在自動(dòng)釋放池的
出棧
(即pop
)操作中- 在頁中
出棧普通對(duì)象
主要是通過next
指針遞減
進(jìn)行的甸怕, - 當(dāng)
頁空
了時(shí),需要賦值頁的parent
對(duì)象為當(dāng)前頁
- 在頁中
綜上所述腮恩,objc_autoreleasePoolPop
出棧的地城流程如下所示
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,如下所示
Mode類型
其中mode
在蘋果文檔中提及的有五個(gè)晰甚,而在iOS中公開暴露出來的只有 NSDefaultRunLoopMode
和 NSRunLoopCommonModes
衙传。 NSRunLoopCommonModes
實(shí)際上是一個(gè) Mode 的集合,默認(rèn)包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode
厕九。
NSDefaultRunLoopMode
:默認(rèn)
的mode蓖捶,正常情況下都是在這個(gè)modeNSConnectionReplyMode
NSModalPanelRunLoopMode
NSEventTrackingRunLoopMode
:使用這個(gè)Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動(dòng))NSRunLoopCommonModes:偽模式,靈活性更好
Source & Timer & Observer
-
Source
表示可以喚醒RunLoop的一些事件
扁远,例如用戶點(diǎn)擊了屏幕俊鱼,就會(huì)創(chuàng)建一個(gè)RunLoop,主要分為Source0
和Source1
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命令獲取
mainRunloop
、currentRunloop
的currentMode
po CFRunLoopCopyCurrentMode(mainRunloop)
-
po CFRunLoopCopyCurrentMode(currentRunloop)
image從這里购公,可以說明萌京,
runloop在運(yùn)行時(shí)的mode只有一個(gè)
-
獲取
mainRunloop
的所有模型,即po CFRunLoopCopyAllModes(mainRunloop)
image從結(jié)果可以驗(yàn)證
runloop
和CFRunloopMode
具有一對(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);
可以得知鹃唯,runloop
與mode
是一對(duì)多
的,同時(shí)可以得出mode
與item
也是一對(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
方法
- 進(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處理不同源的圖示
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í)行流程六剥,如下所示
相關(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_autoreleasePoolPush
和objc_autoreleasePoolPop
,實(shí)際上是調(diào)用AutoreleasePoolPage
的push
和pop
兩個(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í)行