關(guān)于iOS的事件處理機(jī)制解析
推薦 + 挑錯(cuò) + 收藏(0) + 用戶評(píng)論(0)
RunLoop主要處理以下6類事件:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(); static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
Observer事件,runloop中狀態(tài)變化時(shí)進(jìn)行通知。(微信卡頓監(jiān)控就是利用這個(gè)事件通知來(lái)記錄下最近一次main runloop活動(dòng)時(shí)間,在另一個(gè)check線程中用定時(shí)器檢測(cè)當(dāng)前時(shí)間距離最后一次活動(dòng)時(shí)間過(guò)久來(lái)判斷在主線程中的處理邏輯耗時(shí)和卡主線程)。這里還需要特別注意,CAAnimation是由RunloopObserver觸發(fā)回調(diào)來(lái)重繪,接下來(lái)會(huì)講到。
Block事件,非延遲的NSObject PerformSelector立即調(diào)用,dispatch_after立即調(diào)用,block回調(diào)。
Main_Dispatch_Queue事件:GCD中dispatch到main queue的block會(huì)被dispatch到main loop執(zhí)行。
Timer事件:延遲的NSObject PerformSelector,延遲的dispatch_after,timer事件。
Source0事件:處理如UIEvent,CFSocket這類事件。需要手動(dòng)觸發(fā)。觸摸事件其實(shí)是Source1接收系統(tǒng)事件后在回調(diào) __IOHIDEventSystemClientQueueCallback() 內(nèi)觸發(fā)的 Source0,Source0 再觸發(fā)的 _UIApplicationHandleEventQueue()。source0一定是要喚醒runloop及時(shí)響應(yīng)并執(zhí)行的,如果runloop此時(shí)在休眠等待系統(tǒng)的 mach_msg事件,那么就會(huì)通過(guò)source1來(lái)喚醒runloop執(zhí)行。
Source1事件:處理系統(tǒng)內(nèi)核的mach_msg事件。(推測(cè)CADisplayLink也是這里觸發(fā))。
RunLoop執(zhí)行順序的偽代碼
SetupThisRunLoopRunTimeoutTimer(); // by GCD timer //通知即將進(jìn)入runloop__CFRUNLLOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(KCFRunLoopEntry); do { __CFRunLoopDoObservers(kCFRunLoopBeforeTimers); __CFRunLoopDoObservers(kCFRunLoopBeforeSources); __CFRunLoopDoBlocks(); //一個(gè)循環(huán)中會(huì)調(diào)用兩次,確保非延遲的NSObject PerformSelector調(diào)用和非延遲的dispatch_after調(diào)用在當(dāng)前runloop執(zhí)行。還有回調(diào)block __CFRunLoopDoSource0(); //例如UIKit處理的UIEvent事件 CheckIfExistMessagesInMainDispatchQueue(); //GCD dispatch main queue __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); //即將進(jìn)入休眠,會(huì)重繪一次界面 var wakeUpPort = SleepAndWaitForWakingUpPorts(); // mach_msg_trap,陷入內(nèi)核等待匹配的內(nèi)核mach_msg事件 // Zzz.。。 // Received mach_msg, wake up __CFRunLoopDoObservers(kCFRunLoopAfterWaiting); // Handle msgs if (wakeUpPort == timerPort) { __CFRunLoopDoTimers(); } else if (wakeUpPort == mainDispatchQueuePort) { //GCD當(dāng)調(diào)用dispatch_async(dispatch_get_main_queue(),block)時(shí),libDispatch會(huì)向主線程的runloop發(fā)送mach_msg消息喚醒runloop,并在這里執(zhí)行。這里僅限于執(zhí)行dispatch到主線程的任務(wù),dispatch到其他線程的仍然是libDispatch來(lái)處理。 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() } else { __CFRunLoopDoSource1(); //CADisplayLink是source1的mach_msg觸發(fā)? } __CFRunLoopDoBlocks(); } while (!stop && !timeout); //通知observers,即將退出runloop __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBERVER_CALLBACK_FUNCTION__(CFRunLoopExit);
結(jié)合上面的Runloop事件執(zhí)行順序,思考下面代碼邏輯中為什么可以標(biāo)識(shí)tableview是否reload完成
dispatch_async(dispatch_get_main_queue(), ^{ _isReloadDone = NO; [tableView reload]; //會(huì)自動(dòng)設(shè)置tableView layoutIfNeeded為YES,意味著將會(huì)在runloop結(jié)束時(shí)重繪table dispatch_async(dispatch_get_main_queue(),^{ _isReloadDone = YES; }); });
提示:這里在GCD dispatch main queue中插入了兩個(gè)任務(wù),一次RunLoop有兩個(gè)機(jī)會(huì)執(zhí)行GCD dispatch main queue中的任務(wù),分別在休眠前和被喚醒后。
iOS 為什么必須在主線程中操作UI
因?yàn)閁IKit不是線程安全的。試想下面這幾種情況:
兩個(gè)線程同時(shí)設(shè)置同一個(gè)背景圖片,那么很有可能因?yàn)楫?dāng)前圖片被釋放了兩次而導(dǎo)致應(yīng)用崩潰。
兩個(gè)線程同時(shí)設(shè)置同一個(gè)UIView的背景顏色,那么很有可能渲染顯示的是顏色A,而此時(shí)在UIView邏輯樹(shù)上的背景顏色屬性為B。
兩個(gè)線程同時(shí)操作view的樹(shù)形結(jié)構(gòu):在線程A中for循環(huán)遍歷并操作當(dāng)前View的所有subView,然后此時(shí)線程B中將某個(gè)subView直接刪除,這就導(dǎo)致了錯(cuò)亂還可能導(dǎo)致應(yīng)用崩潰。
iOS4之后蘋果將大部分繪圖的方法和諸如 UIColor 和 UIFont 這樣的類改寫為了線程安全可用,但是仍然強(qiáng)烈建議講UI操作保證在主線程中執(zhí)行。
事件響應(yīng)
蘋果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來(lái)接收系統(tǒng)事件,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收。
SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 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)中完成的。
CALayer
在iOS當(dāng)中,所有的視圖都從一個(gè)叫做UIVIew的基類派生而來(lái),UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖,可以做仿射變換(例如旋轉(zhuǎn)或者縮放),或者簡(jiǎn)單的類似于滑動(dòng)或者漸變的動(dòng)畫。
CALayer類在概念上和UIView類似,同樣也是一些被層級(jí)關(guān)系樹(shù)管理的矩形塊,同樣也可以包含一些內(nèi)容(像圖片,文本或者背景色),管理子圖層的位置。它們有一些方法和屬性用來(lái)做動(dòng)畫和變換。和UIView最大的不同是CALayer不處理用戶的交互。CALayer并不清楚具體的響應(yīng)鏈。
UIView和CALayer是一個(gè)平行的層級(jí)關(guān)系,每一個(gè)UIView都有一個(gè)CALayer實(shí)例的圖層屬性,也就是所謂的backing layer,視圖的職責(zé)就是創(chuàng)建并管理這個(gè)圖層,以確保當(dāng)子視圖在層級(jí)關(guān)系中添加或者被移除的時(shí)候,他們關(guān)聯(lián)的圖層也同樣對(duì)應(yīng)在層級(jí)關(guān)系樹(shù)當(dāng)中有相同的操作。實(shí)際上這些背后關(guān)聯(lián)的Layer圖層才是真正用來(lái)在屏幕上顯示和做動(dòng)畫,UIView僅僅是對(duì)它的一個(gè)封裝,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級(jí)接口。
UIView 的 Layer 在系統(tǒng)內(nèi)部,被維護(hù)著三份同樣的樹(shù)形數(shù)據(jù)結(jié)構(gòu),分別是:
圖層樹(shù)(這里是代碼可以操縱的,設(shè)置屬性的最終值會(huì)立刻在這里更新);
呈現(xiàn)樹(shù)(是一個(gè)中間層,系統(tǒng)就在這一層上更改屬性,進(jìn)行各種渲染操作。比如一個(gè)動(dòng)畫是更改alpha值從0到1,那么在邏輯樹(shù)上此屬性會(huì)被立刻更新為最終屬性1,而在動(dòng)畫樹(shù)上會(huì)根據(jù)設(shè)置的動(dòng)畫時(shí)間從0逐步變化到1);
渲染樹(shù)(其屬性值就是當(dāng)前正被顯示在屏幕上的屬性值);
CADisplayLink 和 NSTimer
NSTimer 其實(shí)就是 CFRunLoopTimerRef。一個(gè) NSTimer 注冊(cè)到 RunLoop 后,RunLoop 會(huì)為其重復(fù)的時(shí)間點(diǎn)注冊(cè)好事件。
RunLoop為了節(jié)省資源,并不會(huì)在非常準(zhǔn)確的時(shí)間點(diǎn)回調(diào)這個(gè)Timer。Timer 有個(gè)屬性叫做 Tolerance (寬容度),標(biāo)示了當(dāng)時(shí)間點(diǎn)到后,容許有多少最大誤差。如果某個(gè)時(shí)間點(diǎn)被錯(cuò)過(guò)了,例如執(zhí)行了一個(gè)很長(zhǎng)的任務(wù),則那個(gè)時(shí)間點(diǎn)的回調(diào)也會(huì)跳過(guò)去,不會(huì)延后執(zhí)行。
RunLoop 是用GCD的 dispatch_source_t 實(shí)現(xiàn)的 Timer。 當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒(méi)有 RunLoop,則這個(gè)方法會(huì)失效。當(dāng)調(diào)用 performSelector:onThread: 時(shí),實(shí)際上其會(huì)創(chuàng)建一個(gè) Timer 加到對(duì)應(yīng)的線程去,同樣的,如果對(duì)應(yīng)線程沒(méi)有 RunLoop 該方法也會(huì)失效。
CADisplayLink 是一個(gè)和屏幕刷新率(每秒刷新60次)一致的定時(shí)器(但實(shí)際實(shí)現(xiàn)原理更復(fù)雜,和 NSTimer 并不一樣,其內(nèi)部實(shí)際是操作了一個(gè) Source)。如果在兩次屏幕刷新之間執(zhí)行了一個(gè)長(zhǎng)任務(wù),那其中就會(huì)有一幀被跳過(guò)去,造成界面卡頓的感覺(jué)。
iOS 渲染過(guò)程
圖2-1
通常來(lái)說(shuō),計(jì)算機(jī)系統(tǒng)中 CPU、GPU、顯示器是以上面這種方式協(xié)同工作的。CPU 計(jì)算好顯示內(nèi)容提交到 GPU,GPU 渲染完成后將渲染結(jié)果放入幀緩沖區(qū),隨后視頻控制器會(huì)按照 VSync 信號(hào)如下圖1-4所示,逐行讀取幀緩沖區(qū)的數(shù)據(jù),經(jīng)過(guò)可能的數(shù)模轉(zhuǎn)換傳遞給顯示器顯示。
圖2-2
在 VSync 信號(hào)到來(lái)后,系統(tǒng)圖形服務(wù)會(huì)通過(guò) CADisplayLink 等機(jī)制通知 App,App 主線程開(kāi)始在 CPU 中計(jì)算顯示內(nèi)容,比如視圖的創(chuàng)建、布局計(jì)算、圖片解碼、文本繪制等。隨后 CPU 會(huì)將計(jì)算好的內(nèi)容提交到 GPU 去,由 GPU 進(jìn)行變換、合成、渲染。隨后 GPU 會(huì)把渲染結(jié)果提交到幀緩沖區(qū)去,等待下一次 VSync 信號(hào)到來(lái)時(shí)顯示到屏幕上。由于垂直同步的機(jī)制,如果在一個(gè) VSync 時(shí)間內(nèi),CPU 或者 GPU 沒(méi)有完成內(nèi)容提交,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。從上圖中可以看到,CPU 和 GPU 不論哪個(gè)阻礙了顯示流程,都會(huì)造成掉幀現(xiàn)象。所以開(kāi)發(fā)時(shí),也需要分別對(duì) CPU 和 GPU 壓力進(jìn)行評(píng)估和優(yōu)化。
iOS 的顯示系統(tǒng)是由 VSync 信號(hào)驅(qū)動(dòng)的,VSync 信號(hào)由硬件時(shí)鐘生成,每秒鐘發(fā)出 60 次(這個(gè)值取決設(shè)備硬件,比如 iPhone 真機(jī)上通常是 59.97)。iOS 圖形服務(wù)接收到 VSync 信號(hào)后,會(huì)通過(guò) IPC 通知到 App 內(nèi)。App 的 Runloop 在啟動(dòng)后會(huì)注冊(cè)對(duì)應(yīng)的 CFRunLoopSource 通過(guò) mach_port 接收傳過(guò)來(lái)的時(shí)鐘信號(hào)通知,隨后 Source 的回調(diào)會(huì)驅(qū)動(dòng)整個(gè) App 的動(dòng)畫與顯示。
非常好我支持^.^
(0) 0%
不好我反對(duì)
(0) 0%
下載地址
關(guān)于iOS的事件處理機(jī)制解析下載
相關(guān)電子資料下載
- iOS17.1可能明天發(fā)布,iOS17.1主要修復(fù)哪些問(wèn)題? 380
- 華為全新鴻蒙蓄勢(shì)待發(fā) 僅支持鴻蒙內(nèi)核和鴻蒙系統(tǒng)應(yīng)用 719
- 蘋果手機(jī)系統(tǒng)iOS 17遭用戶質(zhì)疑 731
- iPhone12輻射超標(biāo)?蘋果推送iOS 17.1解決此事 750
- 傳華為囤積零部件 目標(biāo)明年智能手機(jī)出貨7000萬(wàn)部;消息稱 MiOS 僅限國(guó)內(nèi),小米 28208
- 蘋果推送iOS17.0.3,解決iPhone15Pro系列存在機(jī)身過(guò)熱 216
- Testin云測(cè)兼容和真機(jī)服務(wù)平臺(tái)中上線iPhone 15系列手機(jī) 208
- 利爾達(dá)推出搭載HooRiiOS的Matter模組 145
- 運(yùn)放參數(shù)解析:輸入偏置電流(Ibias)和失調(diào)電流(Ios) 128
- 昆侖太科發(fā)布支持國(guó)產(chǎn)飛騰騰銳D2000芯片的開(kāi)源BIOS固件版本 448