前言
本篇文章會大致分析下自動釋放池(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ī)制如下圖所示??
- 從程序
App啟動
到dyld加載完成
后,主線程對應(yīng)的runloop會處于休眠
狀態(tài)愤炸,等待用戶交互
來喚醒
runloop期揪; - 用戶的每一次
交互
都會啟動
一次runloop,用于處理用戶的所有點(diǎn)擊规个、觸摸
事件等; -
CocoaTouch
在監(jiān)聽到交互事件后姓建,就會創(chuàng)建
自動釋放池诞仓,并將所有延遲釋放
的對象添加
到自動釋放池中; - 在一次
完整
的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_autoreleasePoolPush
和 objc_autoreleasePoolPop
。這兩個函數(shù)就是@autoreleasepool
的作用域{}
對應(yīng)底層的壓棧
和出棧
妓柜。
1.2 底層解析
通過上面對AutoreleasePool入口的定位箱季,我們知道了objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
,現(xiàn)在我們?nèi)サ絆bjc源碼棍掐,首先搜索objc_autoreleasePoolPush
藏雏,看看??
定位到是類AutoreleasePoolPage
,再搜索這個類??
通過注釋作煌,有以下幾點(diǎn)說明
- 自動釋放池是一個
指針集合
掘殴,指向一個棧
結(jié)構(gòu)空間。 -
指針集合
中的指針
是指向要被釋放的對象或者pool_boundary
(之前叫做哨兵
最疆,現(xiàn)在經(jīng)常被稱為邊界
) - 自動釋放池是一個
頁
的結(jié)構(gòu) 杯巨,而且這個頁
是一個雙向鏈表
(含有父節(jié)點(diǎn) 和 子節(jié)點(diǎn)) - 自動釋放池和
線程
有很大的關(guān)系
。
那么努酸,問題來了??
- 自動釋放池是
什么時候創(chuàng)建
的服爷? - 對象是
如何被加入
到自動釋放池的? -
哪些對象
才會被加入
到自動釋放池获诈?
帶著這些問題仍源,我們進(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) --> 包含parent
和 child
嫌吠。再看看AutoreleasePoolPageData
結(jié)構(gòu)體所占內(nèi)存大小,算出來是56字節(jié)
掺炭。
1.2.2 objc_autoreleasePoolPush
接下來我們看看壓棧
操作objc_autoreleasePoolPush
的源碼??
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
還是回到類AutoreleasePoolPage
的push
函數(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;
}
流程不難辫诅,大致分為以下幾步:
- 判斷當(dāng)前是否有池,
- 若沒有竹伸,則創(chuàng)建 -->autoreleaseNewPage
- 有池泥栖,則加入一個邊界對象POOL_BOUNDARY
- 返回池的邊界大小
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);
}
}
分析完autoreleaseFullPage
和 autoreleaseNoPage
之后芭析,現(xiàn)在回頭來看autoreleaseFast
就很簡單了锚扎,大致流程??:
- 獲取當(dāng)前線程中
AUTORELEASE_POOL_KEY
對應(yīng)的頁
- 如果該
頁
存在,且有空間馁启,則壓棧objc - 如果
沒有空間
驾孔,則擴(kuò)容芍秆,加一頁
,再壓棧objc - 如果該
頁
不存在翠勉,則創(chuàng)建一頁
妖啥,其key設(shè)為AUTORELEASE_POOL_KEY
,最后壓棧objc
小結(jié)
綜上所述对碌,objc_autoreleasePoolPush
壓棧的核心流程如下??
- 當(dāng)沒有pool荆虱,即只有空占位符(存儲在線程tls中)時,則
創(chuàng)建頁
朽们,壓棧邊界對象
- 在
頁
中壓棧普通對象
怀读,通過next++
進(jìn)行Page容量的標(biāo)記頁
容量滿了,就去子節(jié)點(diǎn)頁
壓棧- 如果當(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頁最多可存儲504個對象楼咳,第1頁滿了熄捍,會開辟新的一頁- 從第2頁開始,最多可存儲505個對象
- 一頁中所有對象所占內(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()
流程如下:
-
ISA()->hasCustomRR()
-->判斷
當(dāng)前類或者父類
含有默認(rèn)的
retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference方法功炮。 - 有,則直接消息發(fā)送术唬,調(diào)用
autorelease
方法 - 沒有薪伏,則調(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
出棧的核心流程如下??
releaseUntil
,釋放當(dāng)前頁出棧對象obj+obj之前的對象疲恢,之前的頁- 也是通過
next--
標(biāo)記當(dāng)前頁的容量
- 出棧后,判斷當(dāng)前頁的容量是否
少于505/2(一半容量)
瓷胧,是
則刪除子節(jié)點(diǎn)頁面显拳,不是
則刪除孫子節(jié)點(diǎn)頁面
二、RunLoop
RunLoop
也是近年來面試必問的知識點(diǎn)搓萧,而且基本涉及很細(xì)很底層杂数,所以本篇也在此探索一些底層實(shí)現(xiàn)原理。我們對于RunLoop
瘸洛,最關(guān)心的幾個問題無非是以下:
- runloop是什么揍移?
- runloop和線程的關(guān)系?
- 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的作用
- 保持程序的持續(xù)運(yùn)行
- 處理App中的各種事件(觸摸、定時器定铜、performSelector)
- 節(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中公開暴露出來的只有 NSDefaultRunLoopMode
和 NSRunLoopCommonModes
。 而NSRunLoopCommonModes
實(shí)際上是一個 Mode 的集合威蕉,默認(rèn)包括 NSDefaultRunLoopMode
和 NSEventTrackingRunLoopMode
刁俭。
- NSDefaultRunLoopMode:默認(rèn)的mode,正常情況下都是在這個mode
- NSConnectionReplyMode
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode:使用這個Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動)
- 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)
由此可見腹躁,runloop
和 CFRunloopMode
是 一對多的關(guān)系
。
示例驗(yàn)證:mode和Item也是一對多關(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時,都會將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é):
- 為自定義的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)
以上考蕾,是針對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
類型削咆,然后主要分為三種情況:
- 如果是entry,則通知observer敞临,即將進(jìn)入runloop
- 如果是exit态辛,則通過observer,即將退出runloop
- 如果是其他中間狀態(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):AutoreleasePool
和RunLoop
仰坦,根據(jù)cpp和匯編查找入口履植,然后找到底層源碼分析它們的實(shí)現(xiàn)流程,按照這個思路悄晃,我們終于清楚了系統(tǒng)是如何處理自動釋放玫霎,RunLoop是如何處理各種事件源信息。相信大家在面試時碰到這些問題,就胸有成竹庶近,應(yīng)對自如了翁脆,哈哈!
參考:
iOS-底層原理 33:內(nèi)存管理(三)AutoReleasePool & NSRunLoop 底層分析