文章內(nèi)容來自:
深入理解RunLoop
官方文檔-Runloop
Runloop概念
- Runloop本身是一個(gè)對(duì)象(CFRunloopRef或是NSRunloop )
- 管理事件和消息
- 提供一種機(jī)制:接收到消息或是事件處于運(yùn)行狀態(tài)徙缴,否則處于休眠避免CPU資源的浪費(fèi)
Runloop和線程的關(guān)系
線程和Runloop是一一對(duì)應(yīng)的双戳,系統(tǒng)持有一個(gè)全局的CFMutableDictionary來保存線程和Runloop盅视;其中key是線程压汪,value對(duì)應(yīng)CFRunloopRef實(shí)例缠导;
線程的最初創(chuàng)建如果不獲取Runloop是不會(huì)自動(dòng)創(chuàng)建的
Runloop組成
一個(gè)runloop中存在多個(gè)Mode辩块,每個(gè)Mode中存在Source/Observer/Timer 靴寂;每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode轴捎,這個(gè)Mode被稱作 CurrentMode鹤盒。如果需要切換 Mode,只能退出runloop侦副,再重新指定一個(gè) Mode 進(jìn)入侦锯。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響
CFRunloopSourceRef (事件產(chǎn)生)
- source0:包含一個(gè)回調(diào)的函數(shù)指針秦驯,它不能主動(dòng)觸發(fā)事件尺碰,需要調(diào)用函數(shù)將其標(biāo)記,然后再喚醒runloop執(zhí)行译隘;
- source1:包含一個(gè)mach_port和函數(shù)指針亲桥,被用于處理內(nèi)核和其他線程發(fā)送過來的消息,可以直接喚醒runloop固耘;
CFRunloopTimerRef
CFRunloopTimerRef包含一個(gè)時(shí)間長(zhǎng)度和函數(shù)指針两曼,runloop會(huì)注冊(cè)時(shí)間,當(dāng)時(shí)間一到就會(huì)觸發(fā)回調(diào)函數(shù)玻驻;
CFRunloopObserverRef
CFRunloopObserverRef包含一個(gè)函數(shù)指針,它可以監(jiān)控runloop的狀態(tài)偿枕,一旦狀態(tài)改變就會(huì)觸發(fā)回調(diào)函數(shù)璧瞬;
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
Source/Timer/Observer 被統(tǒng)稱為 mode item,一個(gè) item 可以被同時(shí)加入多個(gè) mode渐夸。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的嗤锉。如果一個(gè) mode 中一個(gè) item 都沒有,則 RunLoop 會(huì)直接退出墓塌,不進(jìn)入循環(huán)
Runloop的Mode
CFRunLoopMode 和 CFRunLoop 的結(jié)構(gòu)大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
RunLoop 的內(nèi)部邏輯
當(dāng)以一種Mode啟動(dòng)runloop時(shí)瘟忱,CFRunLoopRunSpecific函數(shù)會(huì)根據(jù)ModeName找到對(duì)應(yīng)的Mode,判斷Mode內(nèi)部是否存在source/observer/timer,如果存在會(huì)調(diào)用CFRunloopRun函數(shù)苫幢,CFRunloopRun內(nèi)部存在一個(gè)while循環(huán)访诱,runloop會(huì)通知observers將要分別啟動(dòng)timer、source0韩肝、source1触菜,一旦啟動(dòng)observers中對(duì)應(yīng)的回調(diào)函數(shù)將會(huì)被調(diào)用;runloop會(huì)通過mach_msg()函數(shù)接收系統(tǒng)消息哀峻,當(dāng)這一切執(zhí)行完畢涡相,當(dāng)前runloop就會(huì)退出哲泊!
AutoreleasePool
一個(gè)runloop循環(huán)可以理解創(chuàng)建一個(gè)AutoreleasePool,在啟動(dòng)和離開時(shí)分別觸發(fā)AutoreleasePool的push和pop函數(shù)催蝗,在此次循環(huán)結(jié)束后完成內(nèi)存的清理工作切威;
事件響應(yīng)
- 蘋果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來接收系統(tǒng)事件,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()
- 觸摸丙号,加速先朦,接近傳感器等幾種 Event,隨后用 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 進(jìn)行處理或分發(fā)遍尺,其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等截酷。通常事件比如 UIButton 點(diǎn)擊、touchesBegin/Move/End/Cancel 事件都是在這個(gè)回調(diào)中完成的乾戏。
事件的分發(fā)有一個(gè)hit-testing的過程迂苛,這樣能夠找到最終處理時(shí)間的控件,進(jìn)而執(zhí)行自定義的邏輯鼓择!
手勢(shì)識(shí)別
- 當(dāng)上面的 _UIApplicationHandleEventQueue() 識(shí)別了一個(gè)手勢(shì)時(shí)三幻,其首先會(huì)調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對(duì)應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理呐能。
- 蘋果注冊(cè)了一個(gè) Observer 監(jiān)測(cè) BeforeWaiting (Loop即將進(jìn)入休眠) 事件念搬,這個(gè)Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會(huì)獲取所有剛被標(biāo)記為待處理的 GestureRecognizer摆出,并執(zhí)行GestureRecognizer的回調(diào)
- 當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí)朗徊,這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。
界面更新
- 當(dāng)在操作 UI 時(shí)偎漫,比如改變了 Frame爷恳、更新了 UIView/CALayer 的層次時(shí),或者手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后象踊,這個(gè) UIView/CALayer 就被標(biāo)記為待處理温亲,并被提交到一個(gè)全局的容器去。
- 蘋果注冊(cè)了一個(gè) Observer 監(jiān)聽 BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件杯矩,回調(diào)去執(zhí)行一個(gè)很長(zhǎng)的函數(shù):
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()栈虚。這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整,并更新 UI 界面
關(guān)于網(wǎng)絡(luò)請(qǐng)求
- CFSocket 是最底層的接口史隆,只負(fù)責(zé) socket 通信节芥。
- CFNetwork 是基于 CFSocket 等接口的上層封裝,ASIHttpRequest 工作于這一層。
- NSURLConnection 是基于 CFNetwork 的更高層的封裝头镊,提供面向?qū)ο蟮慕涌隍纪眨珹FNetworking 工作于這一層。
- NSURLSession 是 iOS7 中新增的接口相艇,表面上是和 NSURLConnection 并列的颖杏,但底層仍然用到了 NSURLConnection 的部分功能 (比如 com.apple.NSURLConnectionLoader 線程),AFNetworking2 和 Alamofire 工作于這一層坛芽。
與 Runloop 相關(guān)的實(shí)例
NSTimer注冊(cè)的事件不被執(zhí)行
日常開發(fā)中留储,與 runLoop 接觸得最近可能就是通過 NSTimer 了。一個(gè) Timer 一次只能加入到一個(gè) RunLoop 中咙轩。我們?nèi)粘J褂玫臅r(shí)候获讳,通常就是加入到當(dāng)前的 runLoop 的 default mode 中,而 ScrollView 在用戶滑動(dòng)時(shí)活喊,主線程 RunLoop 會(huì)轉(zhuǎn)到 UITrackingRunLoopMode 丐膝。而這個(gè)時(shí)候, Timer 就不會(huì)運(yùn)行钾菊。
有如下兩種解決方案:
第一種: 設(shè)置RunLoop Mode帅矗,例如NSTimer,我們指定它運(yùn)行于 NSRunLoopCommonModes ,這是一個(gè)Mode的集合煞烫。注冊(cè)到這個(gè) Mode 下后浑此,無論當(dāng)前 runLoop 運(yùn)行哪個(gè) mode ,事件都能得到執(zhí)行滞详。
第二種: 另一種解決Timer的方法是凛俱,我們?cè)诹硗庖粋€(gè)線程執(zhí)行和處理 Timer 事件,然后在主線程更新UI料饥。