一栈雳、RunLoop的基本概念:
runloop從字面的意思來(lái)看就是:運(yùn)行循環(huán)。
runloop的基本作用:
1、保持程序的持續(xù)運(yùn)行
2媒楼、處理app中各種事件(觸摸事件、定時(shí)器事件戚丸、Selector事件等)
3划址、能節(jié)省CPU,提高程序的性能:該做事的時(shí)候就被喚醒限府,沒(méi)有事情就睡眠
假如沒(méi)有了runloop夺颤,程序會(huì)在main函數(shù)執(zhí)行完畢后退出,正是因?yàn)橛辛藃unloop胁勺,導(dǎo)致主函數(shù)沒(méi)有馬上退出世澜,保證了程序持續(xù)運(yùn)行。簡(jiǎn)單的可以理解為
二署穗、RunLoop對(duì)象
iOS中有2套API來(lái)訪問(wèn)和使用RunLoop
1寥裂、Foundation框架中的NSRunLoop
2、Core Foundation中的CFRunLoop
NSRunLoop是基于CFRunLoop的一層OC包裝案疲。但是NSRunLoop不是線程安全的封恰,而CFRunLoopRef是線程安全的。
三褐啡、RunLoop與線程
1诺舔、每條線程都有唯一的一個(gè)與之相對(duì)應(yīng)的RunLoop對(duì)象
2、主線程的RunLoop由系統(tǒng)自動(dòng)創(chuàng)建备畦,子線程的RunLoop可以手動(dòng)創(chuàng)建低飒。
3、RunLoop在線程結(jié)束的時(shí)候會(huì)被銷(xiāo)毀懂盐。
獲取RunLoop對(duì)象
四逸嘀、RunLoop的結(jié)構(gòu)
首先我們需要知道的是CFRunLoop有五大類(lèi):
1、CFRunLoopRef?
2允粤、CFRunLoopMod
3崭倘、CFRunLoopObserverRef
4翼岁、CFRunLoopSourceRef
5、CFRunLoopTimerRef
下邊我們一一介紹上邊的5大類(lèi):
1司光、CFRunLoopRef:一個(gè)RunLoop對(duì)象琅坡,沒(méi)啥好解釋的。
2残家、CFRunLoopMod榆俺。這個(gè)mode咱們好好聊聊。
CFRunLoopMod代表RunLoop的運(yùn)行模式坞淮。
(1)一個(gè)RunLoop中包含多個(gè)Mode茴晋,每個(gè)Mode中又包含了多個(gè)Source/Timer/Observer。
(2)一個(gè)RunLoop在同一時(shí)間只能處在一種運(yùn)行模式下回窘,這個(gè)模式就是CurrentMode诺擅。
(3)如果切換Mode,只能退出當(dāng)前的RunLoop啡直,主要是為了分隔開(kāi)不同組的Source/Timer/Observer烁涌。
系統(tǒng)默認(rèn)注冊(cè)了5種Mode
這里就遇到一個(gè)面試題:調(diào)用+scheduledTimerWithTimeInterval...的方式觸發(fā)的timer,在滑動(dòng)頁(yè)面上的列表是酒觅,timer會(huì)暫停調(diào)用撮执,為什么?如何解決的舷丹?
答:我們知道抒钱,一個(gè)timer在NSDefaultMode下被觸發(fā),如果這個(gè)時(shí)候拖動(dòng)scrollview的話颜凯,這個(gè)timer就失效了继效,因?yàn)橥蟿?dòng)scrollview,RunLoop的mode切換為UITrackingRunLoopMode装获。如果想要讓一個(gè)定時(shí)器在兩個(gè)模式下都有效有兩種方法:1瑞信、將它加入到兩個(gè)mode中;2穴豫、將timer加入到頂層的RunLoop的commonModeItems集合中凡简,RunLoop會(huì)自動(dòng)將這個(gè)集合中的所有item同步到具有"common"標(biāo)示的mode。
第一種方案的代碼:(是mainRunLoop還是currentMode還是需要自己做判斷的)
第二種方案的代碼:
3精肃、CFRunLoopObserver:RunLoop狀態(tài)的觀察者秤涩,每一個(gè)觀察者都包含一個(gè)回調(diào)(指針函數(shù)),當(dāng)RunLoop的狀態(tài)發(fā)生變化時(shí)司抱,觀察者就能通過(guò)回調(diào)接收這個(gè)變化筐眷。觀察狀態(tài)由以下幾種:
有了這個(gè)觀察者,就有了下邊咱們要說(shuō)到的RunLoop的處理邏輯习柠。(這里是不是想到了ViewController的生命周期匀谣?)
4照棋、CFRunLoopSourceRef:事件產(chǎn)生的地方
按照函數(shù)調(diào)用棧分為兩類(lèi):Source0、Source1
Source0:非基于Port的武翎。只包含一個(gè)回調(diào)函數(shù)指針烈炭,使用時(shí)需要將事件標(biāo)記為待處理:CFRunLoopSourceSignal(source),再調(diào)用CFRunLoopWakeUP(runloop)來(lái)喚醒RunLoop宝恶,使其處理整個(gè)事件
Source1:基于Port的符隙。通過(guò)內(nèi)核和其他線程通信,接收垫毙、分發(fā)系統(tǒng)事件霹疫。
5、CFRunLoopTimerRef:基于時(shí)間的觸發(fā)器综芥。
說(shuō)到定時(shí)器丽蝎,有一種說(shuō)法,說(shuō)RunLoop的timer和GCD中的timer是一個(gè)東西毫痕,其實(shí)不是的征峦。CFRunLoopTimer基本上說(shuō)的就是NSTimer迟几,它受RunLoop Mode的影響消请。
而GCD定時(shí)器不受RunLoop Mode的影響。
五类腮、RunLoop的處理邏輯
上圖右邊是線程的輸入源:(這個(gè)輸入源是不是和上邊說(shuō)的CFRunLoopSourceRef:事件產(chǎn)生的地方有什么聯(lián)系半)
(1)基于端口的輸入源(Port Sources)
(2)自定義輸入源(Custom Sources)
(3)Cocoa執(zhí)行Selector的源(performSelectorxxx方法)
(4)定時(shí)源(Timer Sources)
線程針對(duì)上邊不同的輸入源,又不同的處理機(jī)制
(1)handlePort——處理基于端口的輸入源
(2)customSrc——處理用戶(hù)自定義輸入源
(3)mySelector——處理Selector的源
(4)timerFired——處理定時(shí)源
上邊是官方邏輯蚜枢,下邊的是非官方邏輯缸逃,從非官方邏輯里面我們可以看到我們上邊說(shuō)到的CFRunLoop的五大類(lèi)都在什么時(shí)候用
六、RunLoop的具體使用
(1)事件傳遞與手勢(shì)識(shí)別
對(duì)于硬件事件(觸摸厂抽、鎖屏需频、搖晃)的處理,蘋(píng)果注冊(cè)了一個(gè)基于port的source1筷凤,它的回調(diào)函數(shù)是__IOHIDEventSystemClientQueueCallback()昭殉,事件發(fā)生后,系統(tǒng)將事件包裝成IOHIDEvent對(duì)象藐守,并由mach port分配到對(duì)應(yīng)的APP進(jìn)程中挪丢,隨后觸發(fā)source1的回調(diào),并調(diào)用_UIApplicationHandleEventQueueCallback()進(jìn)行內(nèi)部分發(fā)卢厂,其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等乾蓬,接下來(lái)發(fā)生的響應(yīng)者鏈條了。
對(duì)于手勢(shì)識(shí)別:當(dāng)_UIApplicationHandleQueueCallback()接收到手勢(shì)的時(shí)候慎恒,會(huì)將TouchBegin等事件的回調(diào)打斷任内,隨后會(huì)將這個(gè)手勢(shì)標(biāo)記為待處理狀態(tài)撵渡,同時(shí)注冊(cè)一個(gè)observer,檢測(cè)BeforeWaiting狀態(tài)族奢,當(dāng)RunLoop即將進(jìn)入休眠時(shí)姥闭,其內(nèi)部會(huì)獲取到剛才所有標(biāo)記為待處理的手勢(shì),執(zhí)行_UIGestureRecognizerUpdateQueue()越走。
(2)Autorealease
iOS中autorelease變量什么時(shí)候釋放棚品,應(yīng)該分為兩種情況:
手動(dòng)釋放@autoreleasepool { }中的自動(dòng)釋放變量在當(dāng)前大括號(hào)作用域結(jié)束時(shí)釋放;
系統(tǒng)釋放:在當(dāng)前RunLoop本次Loop結(jié)束后釋放廊敌;
autorelease原理:
(3)頁(yè)面刷新
當(dāng)在操作 UI 時(shí)铜跑,比如改變了 Frame、更新了 UIView/CALayer 的層次時(shí)骡澈,或者手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后锅纺,這個(gè) UIView/CALayer 就被標(biāo)記為待處理,并被提交到一個(gè)全局的容器去肋殴。
蘋(píng)果注冊(cè)了一個(gè) Observer 監(jiān)聽(tīng) BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件囤锉,回調(diào)去執(zhí)行:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整护锤,并更新 UI 界面官地。在這個(gè)函數(shù)之后就在屏幕上看到UI的變化。
圖片刷新(假如界面要刷新N多圖片需要渲染)烙懦,此時(shí)用戶(hù)拖拽UI控件就會(huì)出現(xiàn)卡的效果驱入,我們可以通過(guò)RunLoop實(shí)現(xiàn),只在RunLoop默認(rèn)Mode下下載氯析,也就是拖拽Mode下不刷新圖片)
(4)Timer
可以說(shuō)沒(méi)有RunLoop就不可能實(shí)現(xiàn)定時(shí)器的功能亏较。定時(shí)器的大致原理:設(shè)定一個(gè)時(shí)間點(diǎn),將定時(shí)器加入RunLoop中掩缓,等到達(dá)設(shè)定的時(shí)間點(diǎn)的時(shí)候回喚醒線程處理回調(diào)雪情。
(5)PerfromSeletor:afterDelay:
如果當(dāng)前線程中沒(méi)有RunLoop這個(gè)方法是不會(huì)有效的,本質(zhì)上是在當(dāng)前線程的RunLoop中添加一個(gè)定時(shí)器你辣,當(dāng)時(shí)間點(diǎn)到了會(huì)喚醒RunLoop執(zhí)行回調(diào)巡通。
(6)dispatch_main_queue
當(dāng)調(diào)用dispatch_async(dispatch_get_main_queue(), block)時(shí),libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息绢记,RunLoop會(huì)被喚醒扁达,并從消息中取得這個(gè) block,并在回調(diào)__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()里執(zhí)行這個(gè) block蠢熄。但這個(gè)邏輯僅限于 dispatch 到主線程跪解,dispatch 到其他線程仍然是由 libDispatch 處理的。
(7)保證一個(gè)線程永遠(yuǎn)不死
結(jié)束。謝謝叉讥。
總的來(lái)說(shuō)窘行,這個(gè)RunLoop比RunTime要容易一些,可能是因?yàn)橛玫降牡胤奖容^多图仓,另外能夠看到罐盔,不像RunTime藏的很深的樣子。
該問(wèn)借鑒了:(1)RunLoop
? ? ? ? ? ? ? ? ? ?(2)RunLoop
有興趣的可以去看看這個(gè):深入理解RunLoop
在這里感謝上邊兩位作者救崔,如果有版權(quán)問(wèn)題惶看,可以聯(lián)系我。謝謝六孵。
最后纬黎,哪里不對(duì)的地方可以給我留言,我會(huì)及時(shí)改進(jìn)的劫窒,謝謝大家本今。