iOS開發(fā)面試拿offer攻略之RunLoop篇

<meta charset="utf-8">

1.為什么 NSTimer 有時候不好使?

因?yàn)閯?chuàng)建的 NSTimer默認(rèn)是被加入到了 defaultMode,所以當(dāng) RunloopMode

化時,當(dāng)前的 NSTimer 就不會工作了。

2.AFNetworking 中如何運(yùn)用 Runloop?

RunLoop 啟動前內(nèi)部必須要有至少一個Timer/Observer/Source ,所以AFNetworking[runLoop run] 之前先創(chuàng)建了一個新的NSMachPort 添加進(jìn)去了。

通常情況下,調(diào)用者需要持有這個 NSMachPort (mach_port) 并在外部線程通過這個

port 發(fā)送消息到loop 內(nèi);但此處添加port 只是為了讓RunLoop 不至于退出,并沒有用于實(shí)際的發(fā)送消息。

image
image

當(dāng)需要這個后臺線程執(zhí)行任務(wù)時, AFNetworking通過調(diào)用

[NSObject performSelector:onThread:..] 將這個任務(wù)扔到了后臺線程的 RunLoop

如果你正在跳槽或者正準(zhǔn)備跳槽不妨動動小手,添加一下咱們的交流群1012951431來獲取一份詳細(xì)的大廠面試資料為你的跳槽多添一份保障。

3.autoreleasePool在何時被釋放?

App 啟動后,蘋果在主線程RunLoop 里注冊了兩個Observer 其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler() 。

第一個 Observer監(jiān)視的事件是 Entry (即將進(jìn)入 Loop ),其回調(diào)內(nèi)會調(diào)用

_objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其order 是-2147483647 ,優(yōu)先

級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。

第二個 Observer監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時調(diào)用

objc_autoreleasePoolPop() 和objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池; Exit (即將退出 Loop)時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放

池。這個 Observerorder是 2147483647 ,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。

在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、 Timer 回調(diào)內(nèi)的。這些回調(diào)會被

RunLoop 創(chuàng)建好的AutoreleasePool 環(huán)繞著,所以不會出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建 Pool 了。

4.PerformSelector:afterDelay:這個方法在子線程中是否起作用?為什么?怎么解決?

不起作用,子線程默認(rèn)沒有 Runloop,也就沒有 Timer 。

解決的辦法是可以使用 GCD來實(shí)現(xiàn): Dispatch_after

5.RunLoop 的Mode

關(guān)于 Mode首先要知道一個 RunLoop對象中可能包含多個 Mode,且每次調(diào)用 RunLoop的主函數(shù)時,只能指定其中一個 Mode(CurrentMode)。切換 Mode,需要重新指定一個 Mode。主要是為了分隔開不同的 SourceTimer、 Observer ,讓它們之間互不影響。

當(dāng) RunLoop運(yùn)行在 Mode1上時,是無法接受處理 Mode2Mode3上的Source Timer Observer 事件的總共是有五種 CFRunLoopMode :

kCFRunLoopDefaultMode :默認(rèn)模式,主線程是在這個運(yùn)行模式下運(yùn)行UITrackingRunLoopMode :跟蹤用戶交互事件(用于ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響)

UIInitializationRunLoopMod :在剛啟動App 時第進(jìn)入的第一個Mode ,啟動完成后就不再使用GSEventReceiveRunLoopMode :接受系統(tǒng)內(nèi)部事件,通常用不到kCFRunLoopCommonModes :偽模式,不是一種真正的運(yùn)行模式,是同步Source/Timer/Observer 到多個Mode 中的一種解決方案

6.RunLoop的實(shí)現(xiàn)機(jī)制

對于 RunLoop而言最核心的事情就是保證線程在沒有消息的時候休眠,在有消息時喚醒,以提高程序性能。 RunLoop 這個機(jī)制是依靠系統(tǒng)內(nèi)核來完成的(蘋果操作系統(tǒng)核心組件 Darwin中的 Mach )。

RunLoop 通過mach_msg() 函數(shù)接收、發(fā)送消息。它的本質(zhì)是調(diào)用函數(shù)mach_msg_trap() ,相當(dāng)于是一個系統(tǒng)調(diào)用,會觸發(fā)內(nèi)核狀態(tài)切換。在用戶態(tài)調(diào)用mach_msg_trap() 時會切換到內(nèi)核態(tài);內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的mach_msg() 函數(shù)會完成實(shí)際的工作。

即基于 portsource1,監(jiān)聽端口,端口有消息就會觸發(fā)回調(diào);而 source0,要手動標(biāo)記為待處理和手動喚醒 RunLoop

大致邏輯為:

1、通知觀察者 RunLoop 即將啟動。

2、通知觀察者即將要處理 Timer 事件。

3、通知觀察者即將要處理source0事件。

4、處理 source0 事件。

5、如果基于端口的源( Source1 )準(zhǔn)備好并處于等待狀態(tài),進(jìn)入步驟9。

6、通知觀察者線程即將進(jìn)入休眠狀態(tài)。

7、將線程置于休眠狀態(tài),由用戶態(tài)切換到內(nèi)核態(tài),直到下面的任一事件發(fā)生才喚醒線程。

(1)一個基于 portSource1的事件(圖里應(yīng)該是 source0 )。

(2)一個 Timer到時間了。

(3)RunLoop 自身的超時時間到了。

(4)被其他調(diào)用者手動喚醒。

8、通知觀察者線程將被喚醒。

9、處理喚醒時收到的事件。

(1)如果用戶定義的定時器啟動,處理定時器事件并重啟 RunLoop 。進(jìn)入步驟2。

(2)如果輸入源啟動,傳遞相應(yīng)的消息。

(3)如果 RunLoop被顯示喚醒而且時間還沒超時,重啟 RunLoop 。進(jìn)入步驟2

10、通知觀察者 RunLoop 結(jié)束。

7.怎么創(chuàng)建一個常駐線程?

1、為當(dāng)前線程開啟一個RunLoop(第一次調(diào)用[NSRunLoop currentRunLoop]方法時

實(shí)際是會先去創(chuàng)建一個 RunLoop

2、向當(dāng)前RunLoop 中添加一個Port/Source等維持RunLoop的事件循環(huán)(如果 RunLoopmode 中一個item 都沒有,RunLoop 會退出)

3、啟動該 RunLoop

8.RunLoop的數(shù)據(jù)結(jié)構(gòu)

NSRunLoop(Foundation)CFRunLoop(CoreFoundation) 的封裝,提供了面向?qū)ο蟮?code>API

RunLoop 相關(guān)的主要涉及五個類:

CFRunLoopRunLoop 對象

CFRunLoopMode :運(yùn)行模式

CFRunLoopSource :輸入源/事件源

CFRunLoopTimer :定時源

CFRunLoopObserver :觀察者

1、CFRunLoop

pthread (線程對象,說明 RunLoop 和線程是一一對應(yīng)的)、currentMode(當(dāng)前所處的運(yùn)行模式)、 modes (多個運(yùn)行模式的集合)、 commonModes (模式名稱字符串集合)、

commonModelItems (Observer,Timer,Source 集合)構(gòu)成

2、CFRunLoopMode

name source0 source1 observers timers 構(gòu)成

3、CFRunLoopSource

分為 source0source1 兩種

source0:

即非基于port的,也就是用戶觸發(fā)的事件。需要手動喚醒線程,將當(dāng)前線程從內(nèi)核態(tài)切換到用戶態(tài)

source1:

基于port的,包含一個 mach_port 和一個回調(diào),可監(jiān)聽系統(tǒng)端口和通過內(nèi)核和其他線程發(fā)送的消息,能主動喚醒RunLoop,接收分發(fā)系統(tǒng)事件。具備喚醒線程的能力

4、CFRunLoopTimer

基于時間的觸發(fā)器,基本上說的就是 NSTimer。在預(yù)設(shè)的時間點(diǎn)喚醒 RunLoop執(zhí)行回調(diào)。因?yàn)樗腔?RunLoop的,因此它不是實(shí)時的(就是 NSTimer是不準(zhǔn)確的。 因?yàn)?RunLoop只負(fù)責(zé)分發(fā)源的消息。如果線程當(dāng)前正在處理繁重的任務(wù),就有可能導(dǎo)致 Timer 本次延時,或者少執(zhí)行一次)。

5、CFRunLoopObserver

監(jiān)聽以下時間點(diǎn): CFRunLoopActivity

kCFRunLoopEntry : RunLoop準(zhǔn)備啟動

kCFRunLoopBeforeTimersRunLoop將要處理一些Timer相關(guān)事件

kCFRunLoopBeforeSourcesRunLoop將要處理一些Source事件

kCFRunLoopBeforeWaitingRunLoop將要進(jìn)行休眠狀態(tài),即將由用戶態(tài)切換到內(nèi)核態(tài)

kCFRunLoopAfterWaitingRunLoop被喚醒,即從內(nèi)核態(tài)切換到用戶態(tài)后

kCFRunLoopExitRunLoop退出

kCFRunLoopAllActivities : 監(jiān)聽所有狀態(tài)

6、各數(shù)據(jù)結(jié)構(gòu)之間的聯(lián)系

線程和 RunLoop一一對應(yīng), RunLoopMode是一對多的, Modesource、 timer、 observer 也是一對多的

9.解釋一下 NSTimer

NSTimer 其實(shí)就是 CFRunLoopTimerRef,他們之間是toll-free bridged 的。一個NSTimer 注冊到RunLoop 后,RunLoop 會為其重復(fù)的時間點(diǎn)注冊好事件。例如

10:00, 10:10, 10:20 這幾個時間點(diǎn)。RunLoop 為了節(jié)省資源,并不會在非常準(zhǔn)確的時間點(diǎn)回調(diào)這個 TimerTimer有個屬性叫做 Tolerance (寬容度),標(biāo)示了當(dāng)時間點(diǎn)到后,容許有多少最大誤差。

如果某個時間點(diǎn)被錯過了,例如執(zhí)行了一個很長的任務(wù),則那個時間點(diǎn)的回調(diào)也會跳過去,不會延后執(zhí)行。就比如等公交,如果 10:10時我忙著玩手機(jī)錯過了那個點(diǎn)的公交,那我只能等 10:20 這一趟了。

CADisplayLink 是一個和屏幕刷新率一致的定時器(但實(shí)際實(shí)現(xiàn)原理更復(fù)雜,和NSTimer 并不一樣,其內(nèi)部實(shí)際是操作了一個Source )。如果在兩次屏幕刷新之間執(zhí)行了一個長任務(wù),那其中就會有一幀被跳過去(和 NSTimer相似),造成界面卡頓的感覺。在快速滑動 TableView時,即使一幀的卡頓也會讓用戶有所察覺。

Facebook 開源的AsyncDisplayLink 就是為了解決界面卡頓的問題,其內(nèi)部也用到了 RunLoop

10.解釋一下事件響應(yīng)的過程?

蘋果注冊了一個 Source1(基于 mach port 的) 用來接收系統(tǒng)事件,其回調(diào)函數(shù)為 _IOHIDEventSystemClientQueueCallback() 。

當(dāng)一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework生成一個 IOHIDEvent 事件并由SpringBoard接收。這個過程的詳細(xì)情況可以參考這里。

SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種Event ,隨后用 mach port轉(zhuǎn)發(fā)給需要的 App進(jìn)程。隨后蘋果注冊的那個 Source1 就會觸發(fā)回調(diào),并調(diào)用_UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。

_UIApplicationHandleEventQueue() 會把IOHIDEvent 處理并包裝成UIEvent 進(jìn)行處理或分發(fā),其中包括識別 UIGesture/ / UIWindow 等。通常事件比如 UIButton點(diǎn)擊、 touchesBegin/Move/End/Cancel 事件都是在這個回調(diào)中完成的。

11.解釋一下手勢識別的過程?

當(dāng)上面的 _UIApplicationHandleEventQueue()識別了一個手勢時,其首先會調(diào)用Cancel將當(dāng)前的touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對應(yīng)的UIGestureRecognizer 標(biāo)記為待處理。

蘋果注冊了一個 Observer監(jiān)測 BeforeWaiting ( Loop即將進(jìn)入休眠) 事件,這個

Observer 的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver() ,其內(nèi)部會獲取所有剛被標(biāo)記為待處理的 GestureRecognizer,并執(zhí)行 GestureRecognizer的回調(diào)。

當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時,這個回調(diào)都會進(jìn)行相應(yīng)處理。

12.利用 runloop解釋一下頁面的渲染的過程?

當(dāng)我們調(diào)用 [UIView setNeedsDisplay]時,這時會調(diào)用當(dāng)前 View.layer[view.layer setNeedsDisplay] 方法。

這等于給當(dāng)前的 layer打上了一個臟標(biāo)記,而此時并沒有直接進(jìn)行繪制工作。而是會到當(dāng)前的 Runloop即將休眠,也就是 beforeWaiting時才會進(jìn)行繪制工作。

緊接著會調(diào)用 [CALayer display],進(jìn)入到真正繪制的工作。 CALayer層會判斷自己的 delegate有沒有實(shí)現(xiàn)異步繪制的代理方法 displayer: ,這個代理方法是異步繪制的入口,如果沒有實(shí)現(xiàn)這個方法,那么會繼續(xù)進(jìn)行系統(tǒng)繪制的流程,然后繪制結(jié)束。

CALayer 內(nèi)部會創(chuàng)建一個Backing Store ,用來獲取圖形上下文。接下來會判斷這個layer 是否有delegate 。

如果有的話,會調(diào)用 [layer.delegate drawLayer:inContext:],并且會返回給我們[UIView DrawRect:] 的回調(diào),讓我們在系統(tǒng)繪制的基礎(chǔ)之上再做一些事情。

如果沒有 delegate,那么會調(diào)用 [CALayer drawInContext:]

以上兩個分支,最終 CALayer都會將位圖提交到 Backing Store,最后提交給 GPU。至此繪制的過程結(jié)束。

13.什么是異步繪制?

異步繪制,就是可以在子線程把需要繪制的圖形,提前在子線程處理好。將準(zhǔn)備好的圖像數(shù)據(jù)直接返給主線程使用,這樣可以降低主線程的壓力。

異步繪制的過程

要通過系統(tǒng)的 [view.delegate displayLayer:] 這個入口來實(shí)現(xiàn)異步繪制。

代理負(fù)責(zé)生成對應(yīng)的 Bitmap

設(shè)置該 Bitmap為 layer.contents屬性的值

以上就是本次分享,感謝觀看!
以下文章可以做一個學(xué)習(xí)參考:
GCD面試要點(diǎn)
block面試要點(diǎn)
Runtime面試要點(diǎn)
RunLoop面試要點(diǎn)
內(nèi)存管理面試要點(diǎn)
MVC、MVVM面試要點(diǎn)
網(wǎng)絡(luò)性能優(yōu)化面試要點(diǎn)
網(wǎng)絡(luò)編程面試要點(diǎn)
KVC&KVO面試要點(diǎn)
數(shù)據(jù)存儲面試要點(diǎn)
混編技術(shù)面試要點(diǎn)
設(shè)計模式面試要點(diǎn)
UI面試要點(diǎn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容