runLoop,正如其名插佛,表示一直運(yùn)行著的循環(huán)杠巡。
一般來說,一個(gè)線程只能執(zhí)行一個(gè)任務(wù)雇寇,執(zhí)行完就會(huì)推出氢拥,如果我們需要一種機(jī)制,讓線程能隨時(shí)處理時(shí)間但并不退出锨侯,而runLoop就是這樣一個(gè)機(jī)制嫩海;而這種機(jī)制的關(guān)鍵在于:如何管理消息/消息,如何讓線程在沒有處理消息的時(shí)候休眠以避免資源占用识腿,在有消息的時(shí)刻立刻被喚醒出革。
所以,runLoop實(shí)際上就是一個(gè)對(duì)象渡讼,這個(gè)對(duì)象管理了其需要處理的時(shí)間和消息,并提供了一個(gè)入口函數(shù)來執(zhí)行上面的邏輯骂束。線程執(zhí)行了這個(gè)函數(shù)之后耳璧,就會(huì)一直處于“接收消息-等待-處理”的循環(huán)中,知道這個(gè)循環(huán)結(jié)束展箱,函數(shù)返回旨枯。
ios提供了兩個(gè)這樣的對(duì)象:NSRunLoop和CFRunLoopRef。
一混驰、線程與runLoop
① 直線線程:該線程執(zhí)行的任務(wù)是一條直線攀隔;
② 圓形線程:該線程是一個(gè)圓,不斷循環(huán)栖榨,知道通過某種方式截止昆汹,ios中,圓形線程就是通過runLoop實(shí)現(xiàn)的婴栽。
① runLoop和線程緊密相連满粗,可以說,runLoop是為了線程而生的,沒有線程,就沒有runLoop存在的必要愚争;
② 每個(gè)線程都有其對(duì)應(yīng)的runLoop對(duì)象映皆;
③ 主線程的runLoop是默認(rèn)啟動(dòng)的,而其他線程的runLoop是默認(rèn)沒有啟動(dòng)的轰枝;
二捅彻、RunLoop輸入事件來源
runLoop接收的輸入事件來自兩種來源:輸入源和定時(shí)源;
1.輸入源
傳遞異步事件鞍陨,通常消息來自其他線程或程序步淹。輸入源傳遞異步消息給相應(yīng)的處理程序,并調(diào)用runUntilDate方法來退出诚撵;
當(dāng)你創(chuàng)建輸入源贤旷,需要將其分配給runLoop的一個(gè)或多個(gè)模式,模式只會(huì)在特定事件影響監(jiān)聽的源砾脑。
以下是輸入源的類型:
① 基于端口的輸入源:基于端口的輸入源是有內(nèi)核自動(dòng)發(fā)送;
cocoa和Core Foundation內(nèi)置支持使用端口相關(guān)的對(duì)象和函數(shù)來創(chuàng)建基于端口的源艾杏。
例如:在Core Foundation中韧衣,使用端口相關(guān)的函數(shù)來創(chuàng)建端口和runLoop源;
② 自定義輸入源:自定義源需要人工從其他線程發(fā)送购桑。
Core Foundation中可以使用CFRunLoopSourceRef等來創(chuàng)建源畅铭,也可以使用回調(diào)函數(shù)來配置源。Core Foundation會(huì)在配置源的不同地方調(diào)用回調(diào)函數(shù)勃蜘,處理輸入事件硕噩,在源從runLoop移除的時(shí)候清理它;
③ Cocoa上的selector源
定時(shí)源在預(yù)設(shè)的時(shí)間點(diǎn)同步傳遞消息缭贡,這些消息都會(huì)在特定事件或者重復(fù)的時(shí)間間隔炉擅,定時(shí)源則傳遞消息個(gè)處理線程辉懒,不會(huì)立即退出runLoop。
定時(shí)器并不是實(shí)時(shí)機(jī)制谍失,定時(shí)器和你的runLoop的特定模式相關(guān)眶俩,如果定時(shí)器所在的模式當(dāng)前未被runLoop監(jiān)視,那么定時(shí)器將不會(huì)開始快鱼,知道runLoop運(yùn)行在響應(yīng)的模式下颠印。
三、RunLoop的相關(guān)知識(shí)點(diǎn)
runLoop中使用mode來指定時(shí)間在運(yùn)行循環(huán)中的優(yōu)先級(jí)抹竹,分為:
① NSDefaultRunLoopMode(kCFRunLoopDefaultMode): 默認(rèn)线罕,空閑狀態(tài);
② UITrackingRunLoopMode:scrollView滑動(dòng)時(shí)窃判;
③ UIInitializationRunLoopMode: 啟動(dòng)時(shí)钞楼;
④ NSRunLoopCommonModes(kCFRunLoopCommonModes):mode集合。
ps:其中①和④是蘋果公開的mode兢孝。
源是在合適的同步或異步事件發(fā)生時(shí)觸發(fā)窿凤,而runLoop觀察者則是在runLoop本身運(yùn)行的特定時(shí)候觸發(fā),你可以使用runLoop觀察者為處理某一特定事件或是進(jìn)入休眠的程序做準(zhǔn)備跨蟹■ㄊ猓可以將runLoop觀察者和以下事件關(guān)聯(lián):
① runLoop入口
② runLoop何時(shí)處理一個(gè)定時(shí)器;
③ runLoop何時(shí)處理一個(gè)輸入源窗轩;
④ runLoop何時(shí)進(jìn)入睡眠狀態(tài)夯秃;
⑤ runLoop何時(shí)被喚醒,但在喚醒之前要處理的事件痢艺;
⑥ runLoop終止仓洼;
在創(chuàng)建的時(shí)候,也可以指定runLoop觀察者可以只用一次或者循環(huán)使用堤舒,若只用一次色建,那么它在啟動(dòng)后,會(huì)把自己從runLoop中移除舌缤,而循環(huán)的觀察者不會(huì)箕戳。
每次運(yùn)行runLoop,線程的runLoop會(huì)自動(dòng)處理之前未處理的消息国撵,并通知相關(guān)的觀察者陵吸,具體的順序如下:
① 通知觀察者runLoop已經(jīng)啟動(dòng);
② 通知觀察者任何即將要開始的定時(shí)器介牙;
③ 通知觀察著任何即將啟動(dòng)的非基于端口的源壮虫;
④ 啟動(dòng)任何準(zhǔn)備好的非基于端口的源;
⑤ 如果基于端口的源準(zhǔn)備好并處于等待狀態(tài)环础,立即啟動(dòng)囚似,并進(jìn)入步驟⑨剩拢;
⑥ 通著觀察者線程進(jìn)入休眠;
⑦ 將線程至于休眠知道任意下面的事件發(fā)生:
A.某一時(shí)間到達(dá)基于端口的源谆构;
B.定時(shí)器啟動(dòng)裸扶;
C.runLoop設(shè)置的時(shí)間已經(jīng)超過;
D.runLoop被顯示喚醒搬素;
⑧ 通知觀察者線程將被喚醒呵晨;
⑨ 處理未處理的事件
A.如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器并重啟runLoop熬尺,進(jìn)入步驟2
B.如果輸入源啟動(dòng)摸屠,傳遞響應(yīng)的消息;
C.如果runLoop被顯示喚醒粱哼,而且時(shí)間還沒超過季二,重啟runLoop,進(jìn)入步驟2
⑩ 通知觀察者runLoop結(jié)束揭措。
ps:
① 如果事件到達(dá)胯舷,消息會(huì)被傳遞給響應(yīng)的處理程序來處理,runLoop處理完當(dāng)次事件后绊含,runLoop就會(huì)推出桑嘶,而不管之前預(yù)定的時(shí)間到了沒有。
② 可以重新啟動(dòng)runLoop來等待下一事件躬充;
③ 如果線程中有需要處理的源逃顶,但是響應(yīng)的事件沒有到來的時(shí)候,線程就會(huì)休眠等待相應(yīng)事件的發(fā)生充甚。
僅當(dāng)在為你的程序創(chuàng)建輔助線程的時(shí)候以政,你才需要顯示運(yùn)行一個(gè)runLoop
對(duì)于輔助線程,你需要判斷一個(gè)runLoop是否是必須的伴找。如果是必須的盈蛮,那么你要自己配置并啟動(dòng)它,你不需要再任何情況下都去啟動(dòng)一個(gè)線程的runLoop技矮。runLoop在你要和線程有更多的交互時(shí)才需要眉反,比如以下情況:
① 使用端口或者自定義輸入源來和其他線程通信;
② 使用線程的定時(shí)器穆役;
③ Cocoa中使用任何performSelector的方法;
④ 使線程周期性工作梳凛。
四耿币、CFRunLoop介紹
CoreFoundation中有5個(gè)關(guān)于runLoop的類:
① CFRunLoopRef:
② CFRunLoopModeRef:該類并沒有對(duì)外暴露;
③ CFRunLoopSourceRef:時(shí)間產(chǎn)生的地方韧拒,source有兩個(gè)版本:
A.source0:只包含一個(gè)回調(diào)淹接,它并不能主動(dòng)觸發(fā)事件十性,使用時(shí),需先調(diào)用CFRunLoopSourceSignal將這個(gè)source標(biāo)記為待處理塑悼,后調(diào)用CFRunLoopWakeUp來喚醒runLoop劲适,讓其處理這個(gè)事件;
B.source1:包含一個(gè)mach_port和一個(gè)回調(diào)厢蒜,被用于通過內(nèi)核和其他線程相互發(fā)送消息霞势,這種source能主動(dòng)喚醒runLoop線程。
④ CFRunLoopTimerRef:基于事件的觸發(fā)器斑鸦,他和NSTimer是可以混用的愕贡,其包含一個(gè)事件長度和回調(diào),當(dāng)其加入到runLoop中是巷屿,runLoop會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn)固以,當(dāng)時(shí)間點(diǎn)到時(shí),runLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)嘱巾;
⑤ CFRunLoopObserverRef:觀察者憨琳,包含了一個(gè)回調(diào),當(dāng)runLoop的狀態(tài)發(fā)生變化時(shí)旬昭,觀察者就能通過回調(diào)接收到變化篙螟,可觀測的時(shí)間點(diǎn)有:
kCFRunLoopEntry? ? ? ? ? ? ? ? ? ? //即將進(jìn)入Loop
kCFRunLoopBeforeTimers? ? ? ? ? ? ? //即將處理Timer
kCFRunLoopBeforeSources? ? ? ? ? ? //即將處理Source
kCFRunLoopBeforeWaiting? ? ? ? ? ? //即將進(jìn)入休眠
kCFRunLoopAfterWaiting? ? ? ? ? ? ? //剛從休眠中喚醒
kCFRunLoopExit? ? ? ? ? ? ? ? ? ? ? //即將退出Loop
ps:
① 一個(gè)runLoop包含若干個(gè)Mode,每個(gè)Mode包含若干個(gè)Source/Timer/Observer;
② 每次調(diào)用runLoop的主函數(shù)稳懒,只能指定其中一個(gè)Mode闲擦,如果需要切換Mode,只能退出Loop场梆,再重新指定一個(gè)Mode進(jìn)入墅冷;
③ Source/Timer/Observer統(tǒng)稱為mode item,一個(gè)item可以同時(shí)加入多個(gè)mode或油,但一個(gè)item被重復(fù)加入同一個(gè)mode是沒效果的寞忿;
④ 如果一個(gè)mode鐘一個(gè)item都沒有,runLoop就會(huì)退出顶岸,不進(jìn)入循環(huán)腔彰。
CFRunLoop的結(jié)構(gòu)如下
struct __CFRunLoop {
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
...
};
CFRunLoopMode的結(jié)構(gòu)如下:
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
...
};
其中,CFRunLoop對(duì)外暴露的管理Mode的接口有兩個(gè):
CFRunLoopAddCommonMode
CFRunLoopRunInMode
Mode暴露的管理mode item的接口有下面幾個(gè):
CFRunLoopAddSource
CFRunLoopAddObserver
CFRunLoopAddTimer
CFRunLoopRemoveSource
CFRunLoopRemoveObserver
CFRunLoopRemoveTimer
ps:
① 只能通過mode name來操作內(nèi)部的mode辖佣,當(dāng)你傳入一個(gè)新的mode name但runLoop內(nèi)部沒有對(duì)應(yīng)的Mode時(shí)霹抛,runLoop會(huì)自動(dòng)幫你創(chuàng)建對(duì)應(yīng)的CFRunlLoopModeRef。對(duì)于一個(gè)runLoop來說卷谈,其內(nèi)部的mode只能增加杯拐,而不能刪除。
② commonModes:一個(gè)mode可以將自己標(biāo)記為“Common”蘇醒,每當(dāng)runLoop的內(nèi)容發(fā)生變化時(shí)端逼,runLoop都會(huì)自動(dòng)將_commonModeItems里的item同步到具有“Common”標(biāo)記的所有Mode里朗兵。
實(shí)際上,runLoop就是一個(gè)函數(shù)顶滩,其內(nèi)部是一個(gè)do-while循環(huán)余掖,當(dāng)你調(diào)用CFRunLoop()時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)中礁鲁;直到超時(shí)或被手動(dòng)停止盐欺,該函數(shù)才會(huì)返回。
五救氯、runLoop的底層實(shí)現(xiàn)
runLoop的核心是基于mach port的找田,其進(jìn)入休眠時(shí)調(diào)用的函數(shù)是mach_msg(),為了解釋這個(gè)邏輯着憨,需要介紹下ios的系統(tǒng)框架墩衙;
蘋果官方將系統(tǒng)大致劃分為4個(gè)層次:
① 應(yīng)用層:包括用戶能解除到的圖層應(yīng)用,例如Spotlight甲抖、Aqua漆改、SpringBoard等
② 應(yīng)用框架層:即開發(fā)人員解除到的Cocoa等框架;
③ 核心框架層:包括各種核心框架准谚、OpenGL等內(nèi)容挫剑;
④ Darwin:即操作系統(tǒng)的核心,包括系統(tǒng)內(nèi)核柱衔、驅(qū)動(dòng)樊破、Shell等內(nèi)容,這層是開源的
其中唆铐,在硬件層上面的三個(gè)組成部分:Mach哲戚,BSD,IOKit艾岂,共同組成了XNU內(nèi)核顺少。
① Mach:XNU內(nèi)核的內(nèi)環(huán)被稱作Mach,其作為一個(gè)為內(nèi)核王浴,僅提供了諸如處理器調(diào)度脆炎、IPC(進(jìn)程間通信)等非常少量的基礎(chǔ)服務(wù);
② BSD:BSD層可以看做圍繞Mach層的一個(gè)外環(huán)氓辣,提供了諸如進(jìn)程管理秒裕、未見系統(tǒng)和網(wǎng)絡(luò)等功能;
③ IOKit:該層為設(shè)備驅(qū)動(dòng)提供了一個(gè)面向?qū)ο螅–++)的框架钞啸。
Mach本身提供的API非常有限几蜻,而且蘋果也不鼓勵(lì)使用Mach的API癞松,但是這些API非常基礎(chǔ)入蛆,如果沒有這些API的話,其他任何工作都無法實(shí)施硕勿。在Mach中哨毁,所有的東西都是通過自己的對(duì)象實(shí)現(xiàn)的,進(jìn)程源武、線程和虛擬內(nèi)存都被稱為“對(duì)象”扼褪。和其他架構(gòu)不公,Mach對(duì)象間不能直接調(diào)用粱栖,只能通過消息傳遞的方式實(shí)現(xiàn)對(duì)象間的通信话浇。“消息”是Mach中最基礎(chǔ)的概念闹究,消息在兩個(gè)端口(port)之間傳遞幔崖,就是Mach的IPC的核心。
為了實(shí)現(xiàn)消息的發(fā)送和接收渣淤,mach_msg()函數(shù)實(shí)際上是調(diào)用了一個(gè)Mach陷阱(trap)即函數(shù)mach_msg_trap()赏寇,陷阱這個(gè)概念在Mach中等同于系統(tǒng)調(diào)用。當(dāng)你在用戶狀態(tài)調(diào)用mach_msg_trap()時(shí)會(huì)觸發(fā)陷阱機(jī)制价认,切換到內(nèi)核態(tài)嗅定;內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)mach_msg()函數(shù)會(huì)完成實(shí)際的工作。
ps:runLoop的核心就是一個(gè)mach_msg()用踩,runLoop調(diào)用這個(gè)函數(shù)去接收消息渠退,如果沒有別人發(fā)送port消息過來,內(nèi)核會(huì)將線程置于等待撞他脐彩。
例如你在模擬器里跑起一個(gè)app碎乃,然后在app靜止時(shí)點(diǎn)擊暫停,你會(huì)看到主線程調(diào)用棧是停留在mach_msg_trap()這個(gè)地方丁屎。
六荠锭、蘋果用runLoop實(shí)現(xiàn)的功能
app啟動(dòng)后,蘋果在主線程的runLoop里注冊(cè)了兩個(gè)Observer:
① 第一個(gè)Observer監(jiān)視事件是Entry(即將進(jìn)入Loop)晨川,其回調(diào)內(nèi)會(huì)創(chuàng)建自動(dòng)釋放池证九,優(yōu)先級(jí)最高,保證釋放池發(fā)生在所有回調(diào)之前共虑;
② 第二個(gè)Observer監(jiān)視了兩個(gè)事件愧怜,BeforeWaiting(準(zhǔn)備進(jìn)入休眠)時(shí)釋放舊的池并創(chuàng)建新的池;Exit(即將推出loop)時(shí)釋放自動(dòng)釋放池妈拌;優(yōu)先級(jí)最低拥坛,保證其釋放池發(fā)生在其他回調(diào)之后蓬蝶。
蘋果注冊(cè)了一個(gè)Source1(基于mach port)用來接收系統(tǒng)事件
當(dāng)一個(gè)硬件事件(觸摸/搖晃等)發(fā)生后,首先由IOKit.framework生成一個(gè)IOHIDEvent
事件猜惋,并由SpringBoard接收丸氛,隨后用mach port轉(zhuǎn)發(fā)給需要的App進(jìn)程。隨后蘋果注冊(cè)
的那個(gè)Source1會(huì)觸發(fā)回調(diào)著摔,并調(diào)用方法UIApplicationHandleEventQueue()進(jìn)行應(yīng)用內(nèi)
部的分發(fā)缓窜,
UIApplicationHandleEventQueue()方法會(huì)把IOHIDEvent處理并包裝成UIEvent分發(fā),
通常事件比如UIButton點(diǎn)擊谍咆,touch事件都是在這個(gè)回調(diào)中完成的禾锤。
當(dāng)上邊的UIApplicationHandleEventQueue()識(shí)別了手勢后,首先會(huì)打斷當(dāng)前的touch系統(tǒng)回調(diào)摹察,隨后系統(tǒng)將手勢標(biāo)記為待處理恩掷。蘋果注冊(cè)了一個(gè)Observer檢測BeforeWaiting,在這個(gè)事件的回調(diào)函數(shù)中供嚎,獲取所有剛被標(biāo)記為待處理的手勢黄娘,并執(zhí)行手勢的回調(diào)。
當(dāng)操作UI時(shí)查坪,比如改變了Frame等寸宏,這個(gè)UIView/CALayer會(huì)被標(biāo)記為待處理,并提交到一個(gè)全局的容器中偿曙。
蘋果注冊(cè)了一個(gè)Observer監(jiān)聽BeforeWaiting和Exit氮凝,在回調(diào)用,會(huì)遍歷所有待處理的UIView/CALayer以執(zhí)行實(shí)際的繪制和調(diào)整望忆,并更新UI界面罩阵。
一個(gè)NSTimer注冊(cè)到RunLoop后,runLoop會(huì)為其重復(fù)的時(shí)間點(diǎn)注冊(cè)號(hào)時(shí)間启摄,runLoop為了節(jié)省資源稿壁,并不會(huì)再非常準(zhǔn)確的時(shí)間點(diǎn)回調(diào)這個(gè)timer。timer有個(gè)屬性叫做tolerance(寬容度)歉备,表示當(dāng)時(shí)間點(diǎn)后傅是,容許有多少最大誤差。
當(dāng)調(diào)用NSObject的PerformSelector方法后蕾羊,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè)timer并添加到當(dāng)前線程的runLoop中喧笔,如果當(dāng)前線程沒有runLoop,則這個(gè)方法會(huì)失效龟再。
實(shí)際上runLoop底層书闸,也用到了GCD的東西,比如runLoop用dispatch_source_t實(shí)現(xiàn)
的timer利凑,但同時(shí)GCD提供的某些方法也用到了runLoop浆劲,例如dispatch_async()嫌术。
8.關(guān)于網(wǎng)絡(luò)請(qǐng)求
關(guān)于網(wǎng)絡(luò)請(qǐng)求的接口,自上之下有如下基層:
① CFSocket:是最底層的接口牌借,只負(fù)責(zé)socket的通信度气;
② CFNetwork:是基于CFSocket等接口的上層封裝,ASIHttpRequest工作與這層膨报;
③ NSURLConnection:是基于CFNetwork的更高層的封裝蚯嫌,提供面向?qū)ο蟮慕涌冢?/p>
AFNetworking工作于這一層;
④ NSURLSession:是ios7中新增的接口丙躏,表面上和NSURLConnection并列,但底層
仍然用到了NSURLConnection的部分功能束凑,AFNetworking2和Alamofire工作于這層晒旅。
通常使用 NSURLConnection 時(shí),你會(huì)傳入一個(gè) Delegate汪诉,當(dāng)調(diào)用了 [connection start] 后废恋,這個(gè) Delegate 就會(huì)不停收到事件回調(diào)。實(shí)際上扒寄,start 這個(gè)函數(shù)的內(nèi)部會(huì)獲取CurrentRunLoop鱼鼓,然后在其中的 DefaultMode 添加了4個(gè) Source0 (即需要手動(dòng)觸發(fā)的Source) 。CFMultiplexerSource 是負(fù)責(zé)各種 Delegate 回調(diào)的该编,CFHTTPCookieStorage 是處理各種 Cookie 的迄本。
當(dāng)開始網(wǎng)絡(luò)傳輸時(shí),我們可以看到 NSURLConnection 創(chuàng)建了兩個(gè)新線程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private课竣。其中 CFSocket 線程是處理底層 socket 連接的嘉赎。NSURLConnectionLoader 這個(gè)線程內(nèi)部會(huì)使用 RunLoop 來接收底層 socket 的事件,并通過之前添加的 Source0 通知到上層的 Delegate于樟。
NSURLConnectionLoader 中的 RunLoop 通過一些基于 mach port 的 Source 接收來自底層 CFSocket 的通知公条。當(dāng)收到通知后,其會(huì)在合適的時(shí)機(jī)向 CFMultiplexerSource 等 Source0 發(fā)送通知迂曲,同時(shí)喚醒 Delegate 線程的 RunLoop 來讓其處理這些通知靶橱。
CFMultiplexerSource 會(huì)在 Delegate 線程的 RunLoop 對(duì) Delegate 執(zhí)行實(shí)際的回調(diào)。