本文主講RunLoop相關(guān)面試問題并蝗,包括RunLoop概念祭犯、數(shù)據(jù)結(jié)構(gòu)秸妥、事件循環(huán)機(jī)制、RunLoop與NSTimer沃粗、RunLoop與多線程粥惧。
一、RunLoop概念
1最盅、 問題:什么是RunLoop?
答:RunLoop是通過內(nèi)部維護(hù)
的事件循環(huán)來對事件/消息進(jìn)行管理
的一個(gè)對象突雪。
1、沒有消息需要處理時(shí)涡贱,休眠以避免資源占有咏删。
2、有消息需要處理時(shí)问词,立刻被喚醒督函。
2、EventLoop
沒有消息需要被處理時(shí), 系統(tǒng)會(huì)將當(dāng)前線程所有權(quán)轉(zhuǎn)化為內(nèi)核態(tài)
, 當(dāng)有消息需要處理時(shí), 系統(tǒng)會(huì)將當(dāng)前線程的狀態(tài)切換回用戶態(tài)
.
所以RunLoop的循環(huán)并不是一個(gè)單純的死循環(huán), 而是通過狀態(tài)切換, 達(dá)到?jīng)]有消息時(shí)休眠, 有消息時(shí)喚醒的這樣一個(gè)事件循環(huán)機(jī)制.
在沒有消息處理時(shí)戏售,休眠以避免資源占用侨核,它的狀態(tài)切換是怎么樣的?
沒有消息處理 是從
用戶態(tài)
通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核態(tài)
.
也就是當(dāng)沒有消息要處理時(shí),進(jìn)程或者說線程會(huì)進(jìn)入一個(gè)休眠狀態(tài)灌灾,而休眠狀態(tài)的一個(gè)過渡相當(dāng)于是把當(dāng)前線程的控制權(quán)轉(zhuǎn)移給了內(nèi)核態(tài)
當(dāng)有消息需要處理時(shí)就會(huì)被立刻喚醒
實(shí)際上就是由內(nèi)核態(tài)
到用戶態(tài)
的一個(gè)狀態(tài)切換
用戶態(tài)和內(nèi)核態(tài)的介紹:
- 應(yīng)用程序一般都是運(yùn)行在
用戶態(tài)
上面搓译,也就是用戶進(jìn)程包括開發(fā)所使用的絕大多數(shù)的 API 都是針對于用戶層面的. - 而當(dāng)發(fā)生了系統(tǒng)調(diào)用,需要使用一些關(guān)于操作系統(tǒng)锋喜,以及一些底層內(nèi)核相關(guān)的一些指令或者 API 的話些己,就觸發(fā)了系統(tǒng)調(diào)用,而有些系統(tǒng)調(diào)用就會(huì)發(fā)生狀態(tài)空間的切換嘿般,這種切換空間或者說之所以區(qū)分
用戶態(tài)
和內(nèi)核態(tài)
實(shí)際上是對·計(jì)算機(jī)的一些資源調(diào)度·段标,包括資源管理進(jìn)行一個(gè)統(tǒng)一或者說一致性的操作,這樣的話就可以合理的安排資源調(diào)度炉奴,包括可以避免一些特殊的異常 - 比如說在內(nèi)核態(tài)往往有一些線性指令逼庞,中斷,包括一些開機(jī)關(guān)機(jī)的一些操作瞻赶,如果說每一個(gè)用戶進(jìn)程可以假想是一個(gè) app赛糟,每一個(gè) app 都可以促使當(dāng)前用戶手機(jī)關(guān)機(jī)或者說中斷,這種場景是無法想象的砸逊,所以要有一個(gè)用戶態(tài)到內(nèi)核態(tài)上面的一個(gè)區(qū)分璧南,同時(shí)內(nèi)核態(tài)里面的一些內(nèi)容可以對用戶態(tài)當(dāng)中的一些線程進(jìn)行調(diào)度和管理包括進(jìn)程間的一些通信
問題:main函數(shù)為什么能保證一直運(yùn)行狀態(tài)不退出?
答:
- 在main函數(shù)中調(diào)用UIApplicationMain()函數(shù), 這個(gè)函數(shù)內(nèi)部會(huì)啟動(dòng)一個(gè)主線程的RunLoop
- RunLoop是對事件循環(huán)的維護(hù)機(jī)制, 可以不斷的接收消息,比如說點(diǎn)擊屏幕的事件师逸,滑動(dòng)列表司倚,及處理網(wǎng)絡(luò)請求的返回,那么接收消息之后對這個(gè)事件進(jìn)行處理,處理完之后就會(huì)再進(jìn)行等待, 通過用戶態(tài)到內(nèi)核態(tài)的切換, 從而避免資源占用, 讓當(dāng)前線程處于休眠狀態(tài).
- 注意:等待 != 死循環(huán) 动知,RunLoop的循環(huán)通過狀態(tài)切換, 達(dá)到?jīng)]有消息時(shí)休眠,用戶態(tài)切換到內(nèi)核態(tài),有消息時(shí)喚醒,內(nèi)核態(tài)切換到用戶態(tài)的這樣一個(gè)事件循環(huán)機(jī)制
什么是事件循環(huán)皿伺,事件循環(huán)的機(jī)制是怎樣的?
- 維護(hù)的事件循環(huán)可以用來不斷的處理消息或者說事件盒粮,對他們進(jìn)行管理
- 同時(shí)當(dāng)沒有消息需要管理時(shí)用從用戶態(tài)切換到內(nèi)核態(tài)心傀,由此可以用來進(jìn)行當(dāng)前線程的休眠,然后避免資源占用
- 同時(shí)當(dāng)有消息需要處理時(shí)拆讯,會(huì)發(fā)生從內(nèi)核態(tài)到用戶態(tài)的切換,然后當(dāng)前的用戶線程會(huì)被喚醒
- 所以狀態(tài)的切換才是 RunLoop 的關(guān)鍵點(diǎn)
二养叛、RunLoop的數(shù)據(jù)結(jié)構(gòu)
RunLoop開源代碼地址
在 OC 中實(shí)際提供了兩個(gè) RunLoop 的种呐。
一個(gè)是 NSRunLoop,一個(gè)是 CFRunLoop弃甥。
NSRunLoop 是對 CFRunLoop 的封裝爽室,提供了一些面向?qū)ο蟮?API。
NSRunLoop 是位于 Foundation 當(dāng)中的淆攻,CFRunLoop 位于 CoreFoundation 當(dāng)中的阔墩。
RunLoop 的數(shù)據(jù)結(jié)構(gòu)主要有三個(gè):
- CFRunLoop
- CFRunLoopMode
- Source/Timer/Observer
1、CFRunLoop
-
CFRunLoop數(shù)據(jù)結(jié)構(gòu) 由五部分組成: pthread 瓶珊、currentMode啸箫、modes、commonModes伞芹、commonModeItems
CFRunLoop數(shù)據(jù)結(jié)構(gòu)
//源碼
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
- (1)忘苛、pthread:一一對應(yīng)(RunLoop和線程的關(guān)系)
- (2)、currentMode:CFRunLoopMode
- (3)唱较、modes:NSMutableSet<CFRunLoopMode*>
- (4)扎唾、commonModes:NSMutableSet<NSString*>
1>、commonMode不是實(shí)際存在的一種Mode南缓。
2>胸遇、是同步Source/Timer/Observer到多個(gè)Mode中的一種技術(shù)方案。 - (5)汉形、commonModeItems:包含Observer纸镊、Timer、Source
2获雕、CFRunLoopMode數(shù)據(jù)結(jié)構(gòu)
//源碼
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
- name: 對應(yīng)某一個(gè)runloopMode名稱(如 NSDefaultRunLoopMode)
- sources0: MutableSet 集合類型數(shù)據(jù)結(jié)構(gòu)
- sources1: MutableSet 集合類型數(shù)據(jù)結(jié)構(gòu)
- observers: MutableArray
- timers: MutableArray
3薄腻、Source/Timer/Observer
- 在 CF 框架當(dāng)中官方名稱叫 CFRunLoopSource ,有兩種 source0 和 source1
喚醒線程就是從內(nèi)核態(tài)切換到用戶態(tài)
1>届案、CFRunLoopSource
source0:需要手動(dòng)喚醒線程庵楷。
source1:具備喚醒線程的能力。
source0: 非系統(tǒng)事件.
只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件尽纽。使用時(shí)咐蚯,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理弄贿,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop春锋,讓其處理這個(gè)事件。
source1 : 系統(tǒng)事件
包含了一個(gè) mach_port和一個(gè)回調(diào)(函數(shù)指針)差凹,被用于通過內(nèi)核和其他線程相互發(fā)送消息期奔。這種 Source 能主動(dòng)喚醒 RunLoop 的線程
2>、CFRunLoopTimer
基于事件的定時(shí)器危尿,和NSTimer是toll-free bridged的呐萌。和平時(shí)所使用的 NSTimer 是具備免費(fèi)橋轉(zhuǎn)換的
3>、CFRunLoopObserver
某個(gè)observer可以監(jiān)聽runloop的狀態(tài)變化谊娇,并作出一定反應(yīng)肺孤。
觀測時(shí)間點(diǎn)
- KCFRunLoopEntry (RunLoop入口時(shí)機(jī))
- KCFRunLoopBeforeTimers (通知觀察者RunLoop將要對timer一些相關(guān)事件進(jìn)行處理)
- KCFRunLoopBeforeSources (將要對處理一些sources事件)
- KCFRunLoopBeforeWaiting (通知觀察者RunLoop將要進(jìn)入休眠狀態(tài), 即將要發(fā)用戶態(tài)到內(nèi)核態(tài)切換)
- KCFRunLoopAfterWaiting (內(nèi)核態(tài)到用戶態(tài)切換不久)
- KCFRunLoopExit (RunLoop退出通知)
可以通過注冊一些 Observer 來實(shí)現(xiàn)對 RunLoop 的一些相關(guān)時(shí)間點(diǎn)的監(jiān)測或者觀察。
4济欢、各個(gè)數(shù)據(jù)結(jié)構(gòu)之間關(guān)系
問題:RunLoop 和Mode以及 Mode和其對應(yīng)的 Source ,Timer , Observer 有什么關(guān)系?
答:
- 一個(gè)runLoop可以有多個(gè)mode, 一對多關(guān)系
- mode 和Source, Timer, Observer 是一對多的關(guān)系
可以看到, 一個(gè)RunLoop可以有多個(gè)Mode, 而每個(gè)Mode中又可以存放多個(gè)不同的事件, 我們在切換Mode時(shí), 其他Mode的事件將不會(huì)被響應(yīng).
5赠堵、RunLoop的Mode
Runloop Mode 實(shí)際上是 Source,Timer 和 Observer 的集合法褥,不同的 Mode 把不同組的 Source茫叭,Timer 和 Observer 隔絕開來。Runloop 在某個(gè)時(shí)刻只能跑在一個(gè) Mode 下,處理這一個(gè) Mode 當(dāng)中的 Source,Timer 和 Observer典唇。
蘋果文檔中提到的 Mode 有五個(gè)膨俐,分別是:
- NSDefaultRunLoopMode:默認(rèn)的mode,正常情況下都是在這個(gè)mode
- NSConnectionReplyMode
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode:使用這個(gè)Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動(dòng))
- NSRunLoopCommonModes
iOS 中公開暴露出來的只有 NSDefaultRunLoopMode
和 NSRunLoopCommonModes
。 NSRunLoopCommonModes 實(shí)際上是一個(gè) Mode 的集合,默認(rèn)包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。
問題:RunLoop為什么會(huì)有多個(gè)mode?
答:
6烁登、CommonModes的特殊性
NSRunLoopCommonModes
(問題:CommonModes是否使用過, 你對CommonModes事怎樣理解?)
- CommonModes
不是實(shí)際存在
的一種Mode - 是同步Source/Timer/Observer到多個(gè)Mode中的
一種技術(shù)方案
在 OC 當(dāng)中經(jīng)常會(huì)通過 NSRunLoopCommonModes 字符串常量來表達(dá) CommonMode。
- CommonModes實(shí)現(xiàn):一個(gè) Mode 可以將自己標(biāo)記為”Common”屬性(通過將其 ModeName 添加到 RunLoop 的 “commonModes” 中)蔚舀。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時(shí)饵沧,RunLoop 都會(huì)自動(dòng)將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 標(biāo)記的所有Mode里。
三赌躺、RunLoop事件循環(huán)機(jī)制的實(shí)現(xiàn)
在開發(fā)過程中調(diào)用的 NSRunLoop 的 run 系列的相關(guān)方法以及 CFRunLoop 的相關(guān)的 run 方法最終都會(huì)調(diào)用到 CFRunLoopRun() 函數(shù)
1 事件循環(huán)的整體邏輯:
1)在 RunLoop 啟動(dòng)之后首先會(huì)發(fā)出一條通知來告訴觀察者當(dāng)前 RunLoop 即將啟動(dòng)
2)之后 RunLoop 將要處理 Timer/Sources0 事件狼牺,發(fā)出通知
3)進(jìn)入正式 Sources0 的處理
4)如果有 Sources1 需要處理,這個(gè)時(shí)候會(huì)用過一條 goto 語句來進(jìn)行代碼邏輯的跳轉(zhuǎn)礼患,來處理喚醒時(shí)收到的消息
5)如果沒有 Source1要處理是钥,此時(shí)線程將要休眠掠归,同時(shí)也會(huì)發(fā)送通知給 Observe,然后就發(fā)生了用戶態(tài)到內(nèi)核態(tài)的切換
6)線程正式進(jìn)入休眠悄泥,等待喚醒
7)之后線程被喚醒虏冻,也要發(fā)送一個(gè)通知,通知觀察著說當(dāng)前線程被喚醒了弹囚,然后處理喚醒時(shí)受到的消息厨相,之后又會(huì)回到第二步
2 當(dāng)處于休眠的runloop,可以通過哪些方式喚醒?
答:
- 通過 Sources1 進(jìn)行當(dāng)前 RunLoop 的喚醒
- Timer 事件的回調(diào)
- 外部手動(dòng)的喚醒
3 App 從啟動(dòng)到退出鸥鹉,這個(gè)過程當(dāng)中系統(tǒng)都發(fā)生了什么蛮穿?
答:
- 調(diào)用了 main 函數(shù)之后,在 main 函數(shù)中會(huì)調(diào)用 UIApplicationMain 函數(shù)毁渗,在這個(gè)函數(shù)內(nèi)部會(huì)啟動(dòng)一個(gè)主線程的 RunLoop绪撵,然后經(jīng)過一系列的處理最終主線程的 RunLoop 處于休眠狀態(tài),
- 如果說此時(shí)點(diǎn)擊一個(gè)屏幕祝蝠,會(huì)產(chǎn)生一個(gè) Responder ,然后基于 Responder 最終會(huì)轉(zhuǎn)成 Sources1幻碱,可以把主線程喚醒绎狭,運(yùn)行然后處理,
- 之后當(dāng)把程序殺死的時(shí)候 RunLoop 就會(huì)退出褥傍,這個(gè)時(shí)候就會(huì)發(fā)出一個(gè)通知即將退出 RunLoop 儡嘶,RunLoop 退出之后線程也就銷毀掉了。
4 RunLoop的核心
main 函數(shù)經(jīng)過一系列的處理之后恍风,內(nèi)部最終會(huì)調(diào)用一個(gè)系統(tǒng)函數(shù) mach_msg() ,于是就發(fā)生了一個(gè)系統(tǒng)調(diào)用蹦狂,經(jīng)過系統(tǒng)調(diào)用當(dāng)前用戶線程就把控制權(quán)轉(zhuǎn)交核心態(tài),然后 mach_msg() 在一定條件下會(huì)返回給調(diào)用方朋贬,觸發(fā)返回的邏輯就是喚醒線程的邏輯凯楔,比如收到了一個(gè) Sources1 或者 Timer 事件的回調(diào),包括外部手動(dòng)喚醒锦募,就可以觸發(fā)核心態(tài)到用戶態(tài)的切換摆屯,那么當(dāng)前app的主線程循環(huán)就會(huì)被喚醒,這就是 RunLoop 的核心
四糠亩、RunLoop與NSTimer
問題:滑動(dòng)TableView的時(shí)候虐骑,我們的定時(shí)器還會(huì)生效嗎?
答:
Runloop Mode 實(shí)際上是 Source赎线,Timer 和 Observer 的集合廷没,不同的 Mode 把不同組的 Source,Timer 和 Observer 隔絕開來垂寥。Runloop 在某個(gè)時(shí)刻只能跑在一個(gè) Mode 下颠黎,處理這一個(gè) Mode 當(dāng)中的 Source另锋,Timer 和 Observer。
滑動(dòng)TableView的時(shí)候Mode發(fā)生切換盏缤,從KCFRunLoopDefaultMode切換到UITrackingRunLoopMode. (當(dāng)把Source/Timer/Observer 添加到某個(gè)Mode上, 如果當(dāng)前runloop運(yùn)行在另一個(gè)mode上, 對應(yīng)Source/Timer/Observer是沒有辦法進(jìn)行后續(xù)處理和回調(diào).)
解決: 通過函數(shù)CFRunLoopAddTimer() 將timer添加到commonMode上.
CommonMode
不是實(shí)際存在
的一種Mode, 只是將一些mode打上common標(biāo)記, 然后可以把某個(gè)事件源(如Timer)同步到多個(gè)mode中.
void CFRunLoopAddTimer(runloop, timer, commonMode)
五砰蠢、RunLoop與多線程
1、怎樣實(shí)現(xiàn)一個(gè)常駐線程?
(1)唉铜、為當(dāng)前線程開啟一個(gè)RunLoop台舱。
(2)、向該RunLoop中添加一個(gè)Port/Source等維持RunLoop的事件循環(huán)潭流。
(3)竞惋、啟動(dòng)該RunLoop。
實(shí)現(xiàn)一個(gè)常駐線程的基本步驟:
- 1)為當(dāng)前線程開啟一個(gè) RunLoop :NSRunLoop *runLoop = [NSRunLoop currentRunLoop];灰嫉,因?yàn)楂@取當(dāng)前 RunLoop 這個(gè)方法本身會(huì)查找如果當(dāng)前線程沒有 RunLoop 的話拆宛,會(huì)在系統(tǒng)的內(nèi)部創(chuàng)建
- 2)如果線程沒有資源或者事件源要處理的話,默認(rèn)情況下是不能維持事件循環(huán)的就會(huì)直接退出了讼撒,所以需要給他添加一個(gè) Port/Source 來維持他的時(shí)間循環(huán)機(jī)制
- 3)然后再調(diào)用 RunLoop 的 run 方法就可以實(shí)現(xiàn)一個(gè)常駐線程
2浑厚、RunLoop和線程關(guān)系
(1)、runloop與線程是一一對應(yīng)的根盒,一個(gè)runloop對應(yīng)一個(gè)核心的線程钳幅,為什么說是核心的,是因?yàn)閞unloop是可以嵌套的炎滞,但是核心的只能有一個(gè)敢艰,他們的關(guān)系保存在一個(gè)全局的字典里。
(2)册赛、runloop是來管理線程的钠导,當(dāng)線程的runloop被開啟后,線程會(huì)在執(zhí)行完任務(wù)后進(jìn)入休眠狀態(tài)森瘪,有了任務(wù)就會(huì)被喚醒去執(zhí)行任務(wù)牡属。
(3)、runloop在第一次獲取時(shí)被創(chuàng)建扼睬,在線程結(jié)束時(shí)被銷毀湃望。
(4)、對于主線程來說痰驱,runloop在程序一啟動(dòng)就默認(rèn)創(chuàng)建好了证芭。
(5)、對于子線程來說担映,runloop是懶加載的废士,只有當(dāng)我們使用的時(shí)候才會(huì)創(chuàng)建。
dispatch_queue_t queue = dispatch_queue_create("com.codeTao.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[self performSelector:@selector(timerAction) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
});
(6)蝇完、子線程中使用定時(shí)器官硝,需將定時(shí)器添加至RunLoop中矗蕊,確保子線程的runloop被創(chuàng)建,不然定時(shí)器不會(huì)回調(diào)氢架。
dispatch_queue_t queue = dispatch_queue_create("com.codeTao.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//此種方式創(chuàng)建的timer已經(jīng)添加到NSRunloop中了
NSTimer *timer1 =[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
//此種方式創(chuàng)建的timer沒有添加至runloop中
NSTimer *timer2 = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
六 RunLoop面試總結(jié)
問題:什么是RunLoop? 它是怎樣做到有事做事,沒事休息?
答: RunLoop是通過內(nèi)部維護(hù)
的事件循環(huán)來對事件/消息進(jìn)行管理
的一個(gè)對象傻咖。
調(diào)用 [CFRunLoop run] 相關(guān)方法后, 會(huì)調(diào)用系統(tǒng)函數(shù)mach_msg, 同時(shí)發(fā)生了用戶態(tài)向核心態(tài)的切換,當(dāng)前線程處于休眠狀態(tài).所有做到有事做事,沒事休息
問題:RunLoop與線程是怎樣的關(guān)系?
答:
- runloop與線程是一一對應(yīng)的
- 一個(gè)線程默認(rèn)是沒有runloop的, 需要手動(dòng)創(chuàng)建
問題:如何實(shí)現(xiàn)一個(gè)常駐線程?
答:三個(gè)步驟:
(1)、創(chuàng)建一個(gè)線程對應(yīng)的RunLoop岖研。
(2)卿操、向該RunLoop中添加一個(gè)Port/Source/Timer/Observer等維持RunLoop的事件循環(huán)。
(3)孙援、啟動(dòng)該RunLoop 害淤。調(diào)用CFRunLoop run方法
注意:運(yùn)行的模式和添加模式必須是同一個(gè),否則外部使用while循環(huán)會(huì)導(dǎo)致死循環(huán)
問題:怎樣保證子線程數(shù)據(jù)回來更新UI的時(shí)候不打斷用戶的滑動(dòng)操作?
答:
- 用戶進(jìn)行滑動(dòng)過程中,當(dāng)前RunLoop運(yùn)行在
UITrackingRunLoopMode下
- 我們一般在子線程中進(jìn)行網(wǎng)絡(luò)請求, 所以可以將子線程拋給主線程數(shù)據(jù)并進(jìn)行UI更新的邏輯封裝起來提交到
主線程
的NSDefaultRunLoopMode下. - 這樣拋回來的任務(wù),當(dāng)用戶滑動(dòng)時(shí)處于UITrackingRunLoopMode下就不會(huì)執(zhí)行任務(wù). 當(dāng)手停止滑動(dòng)操作后, 當(dāng)前線程mode切換到
NSDefaultRunLoopMode
下,再處理子線程上拋給主線程的任務(wù),這樣就不會(huì)打斷用戶滑動(dòng)操作.
問題:我們可以監(jiān)聽 RunLoop 哪些時(shí)間點(diǎn)?
答:
-
KCFRunLoopEntry
RunLoop 的入口時(shí)機(jī)拓售,當(dāng) RunLoop 準(zhǔn)備啟動(dòng)的時(shí)候系統(tǒng)會(huì)給我們一個(gè)回調(diào)通知窥摄,這個(gè)通知掉 CFRunLoopEntry -
KCFRunLoopBeforeTimers
代表的含義:通知觀察者 RunLoop 將要對 Timer 一些相關(guān)事件進(jìn)行處理了 -
KCFRunLoopBeforeSources
代表將要處理一些 Source 事件 -
KCFRunLoopBeforeWaiting
通知對應(yīng)觀察者,當(dāng)前 RunLoop 將要進(jìn)入休眠狀態(tài)础淤,這個(gè)通知或者說觀測點(diǎn)是非常重要的一個(gè)觀測點(diǎn)崭放,在 RunLoop 發(fā)送這個(gè)通知的時(shí)候,即將要發(fā)生用戶態(tài)到內(nèi)核態(tài)的切換 -
KCFRunLoopAfterWaiting
這也是一個(gè)重要的觀測點(diǎn)鸽凶,這個(gè)通知發(fā)出的時(shí)機(jī)恰好是從內(nèi)核態(tài)切換到用戶態(tài)之后的不久之間 -
KCFRunLoopExit
代表 RunLoop 退出的通知
問題:什么時(shí)候使用Runloop?
當(dāng)需要和該線程進(jìn)行交互的時(shí)候才會(huì)使用Runloop
問題: Runloop和線程是什么關(guān)系币砂?
答:
- 每條線程都有唯一的一個(gè)與之對應(yīng)的RunLoop對象,其關(guān)系是保存在一個(gè)全局的 Dictionary 里吱瘩;
- 主線程的RunLoop已經(jīng)自動(dòng)創(chuàng)建,子線程的RunLoop需要手動(dòng)創(chuàng)建迹缀;
- RunLoop在第一次獲取時(shí)創(chuàng)建使碾,在線程結(jié)束時(shí)銷毀
問題:Runloop的mode作用是什么?
答:
指定事件在運(yùn)行循環(huán)中的優(yōu)先級的祝懂,
線程的運(yùn)行需要不同的模式票摇,去響應(yīng)各種不同的事件,去處理不同情境模式砚蓬。(比如可以優(yōu)化tableview的時(shí)候可以設(shè)置UITrackingRunLoopMode下不進(jìn)行一些操作矢门,比如設(shè)置圖片等。)
問題: 以+scheduledTimerWithTimeInterval:
的方式觸發(fā)的timer
灰蛙,在滑動(dòng)頁面上的列表時(shí)祟剔,timer
會(huì)暫停回調(diào)摩梧, 為什么物延?
答:
滑動(dòng)scrollView時(shí),主線程的RunLoop會(huì)切換到UITrackingRunLoopMode
這個(gè)Mode仅父,執(zhí)行的也是UITrackingRunLoopMode
下的任務(wù)(Mode中的item)叛薯,而timer是添加在NSDefaultRunLoopMode
下的浑吟,所以timer任務(wù)并不會(huì)執(zhí)行,只有當(dāng)UITrackingRunLoopMode
的任務(wù)執(zhí)行完畢耗溜,runloop切換到NSDefaultRunLoopMode
后组力,才會(huì)繼續(xù)執(zhí)行timer。
問題: 如何解決在滑動(dòng)頁面上的列表時(shí)抖拴,timer會(huì)暫土亲郑回調(diào)?
答:
將Timer
放到NSRunLoopCommonModes
中執(zhí)行即可
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
問題: NSTimer使用時(shí)需要注意什么城舞?
答:
- 注意timer添加到runloop時(shí)應(yīng)該設(shè)置為什么mode
- 注意timer在不需要時(shí)轩触,一定要調(diào)用invalidate方法使定時(shí)器失效,否則得不到釋放
問題: RunLoop 有哪些應(yīng)用家夺?
答: 常駐內(nèi)存脱柱、AutoreleasePool 自動(dòng)釋放池
問題: AutoreleasePool 和 RunLoop 有什么聯(lián)系?
答:
iOS應(yīng)用啟動(dòng)后會(huì)注冊兩個(gè) Observer 管理和維護(hù) AutoreleasePool拉馋。應(yīng)用程序剛剛啟動(dòng)時(shí)默認(rèn)注冊了很多個(gè)Observer榨为,其中有兩個(gè)Observer的 callout 都是 _ wrapRunLoopWithAutoreleasePoolHandler,這兩個(gè)是和自動(dòng)釋放池相關(guān)的兩個(gè)監(jiān)聽煌茴。
第一個(gè) Observer 會(huì)監(jiān)聽 RunLoop 的進(jìn)入随闺,它會(huì)回調(diào)objc_autoreleasePoolPush() 向當(dāng)前的 AutoreleasePoolPage 增加一個(gè)哨兵對象標(biāo)志創(chuàng)建自動(dòng)釋放池。這個(gè) Observer 的 order 是 -2147483647 優(yōu)先級最高蔓腐,確保發(fā)生在所有回調(diào)操作之前矩乐。
第二個(gè) Observer 會(huì)監(jiān)聽 RunLoop 的進(jìn)入休眠和即將退出 RunLoop 兩種狀態(tài),在即將進(jìn)入休眠時(shí)會(huì)調(diào)用 objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 根據(jù)情況從最新加入的對象一直往前清理直到遇到哨兵對象回论。而在即將退出 RunLoop 時(shí)會(huì)調(diào)用objc_autoreleasePoolPop() 釋放自動(dòng)自動(dòng)釋放池內(nèi)對象散罕。這個(gè)Observer 的 order 是 2147483647 ,優(yōu)先級最低傀蓉,確保發(fā)生在所有回調(diào)操作之后欧漱。
問題:NSRunLoop 和 CFRunLoopRef 區(qū)別?
答: CFRunLoopRef 基于C 線程安全,NSRunLoop 基于 CFRunLoopRef 面向?qū)ο蟮腁PI 是不安全的