簡(jiǎn)述
- 運(yùn)行循環(huán)、跑圈 內(nèi)部是一個(gè)do-while循環(huán) 在這個(gè)循環(huán)內(nèi)部不斷處理各種比如(source timer observer) 有事情就跑一圈看看有沒(méi)有東西要處理 提高程序性能
作用
- 可以實(shí)現(xiàn) RunLoop 實(shí)現(xiàn)自動(dòng)釋放池、延遲回調(diào)、觸摸事件、屏幕刷新等功能的。
RunLoop 對(duì)外的接口
- 在 CoreFoundation 里面關(guān)于 RunLoop 有5個(gè)類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
其中CFRunLoopModeRef類并沒(méi)有對(duì)外暴露,只是通過(guò)CFRunLoopRef的接口進(jìn)行了封裝。他們的關(guān)系如下:

-
一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode,這個(gè)Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入。這樣做主要是為了分隔開(kāi)不同組的 Source/Timer/Observer,讓其互不影響。
-
上面的 Source/Timer/Observer 被統(tǒng)稱為 mode item,一個(gè) item 可以被同時(shí)加入多個(gè) mode。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的。如果一個(gè) mode 中一個(gè) item 都沒(méi)有,則 RunLoop 會(huì)直接退出,不進(jìn)入循環(huán)。
CFRunLoopSourceRef 是事件產(chǎn)生的地方。Source有兩個(gè)版本:Source0 和 Source1。
? Source0 只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件。使用時(shí),你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來(lái)喚醒 RunLoop,讓其處理這個(gè)事件。
? Source1 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過(guò)內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動(dòng)喚醒 RunLoop 的線程,其原理在下面會(huì)講到。
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode
CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào)(函數(shù)指針)。當(dāng)其加入到 RunLoop 時(shí),RunLoop會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)。
CFRunLoopObserverRef是觀察者,每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過(guò)回調(diào)接受到這個(gè)變化??梢杂^測(cè)的時(shí)間點(diǎn)有以下幾個(gè):
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), 1
kCFRunLoopBeforeTimers = (1UL << 1), 2
kCFRunLoopBeforeSources = (1UL << 2), 4
kCFRunLoopBeforeWaiting = (1UL << 5), 32
kCFRunLoopAfterWaiting = (1UL << 6), 64
kCFRunLoopExit = (1UL << 7), 12867
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
RunLoop的Mode
Mode是一種狀態(tài)
-
蘋(píng)果公開(kāi)提供的 Mode 有兩個(gè):kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用這兩個(gè) Mode Name 來(lái)操作其對(duì)應(yīng)的 Mode。
應(yīng)用場(chǎng)景舉例:主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個(gè) Mode 都已經(jīng)被標(biāo)記為”Common”屬性。DefaultMode 是 App 平時(shí)所處的狀態(tài),TrackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)。當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí),Timer 會(huì)得到重復(fù)回調(diào),但此時(shí)滑動(dòng)一個(gè)TableView時(shí),RunLoop 會(huì)將 mode 切換為 TrackingRunLoopMode,這時(shí) Timer 就不會(huì)被回調(diào),并且也不會(huì)影響到滑動(dòng)操作。
-
同時(shí)蘋(píng)果還提供了一個(gè)操作 Common 標(biāo)記的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用這個(gè)字符串來(lái)操作 Common Items,或標(biāo)記一個(gè) Mode 為 “Common”。使用時(shí)注意區(qū)分這個(gè)字符串和其他 mode name。
應(yīng)用場(chǎng)景舉例:有時(shí)你需要一個(gè) Timer,在兩個(gè) Mode 中都能得到回調(diào),一種辦法就是將這個(gè) Timer 分別加入這兩個(gè) Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 “commonModeItems” 中?!眂ommonModeItems” 被 RunLoop 自動(dòng)更新到所有具有”Common”屬性的 Mode 里去。
RunLoop 的內(nèi)部邏輯

- 實(shí)際上就是一個(gè)do-while循環(huán) 當(dāng)你調(diào)用RunLoop并且讓他開(kāi)始Run的時(shí)候線程就會(huì)一直停留在這個(gè)循環(huán)里;直到超時(shí)或被手動(dòng)停止,該函數(shù)才會(huì)返回。
下面是例子
autoreleasepool
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
熟悉嗎?是的。每個(gè)程序被創(chuàng)建我們都能在main文件中看到這一行,在這里UIApplicationMain表示主線程已經(jīng)創(chuàng)建了一個(gè)RunLoop, 線程結(jié)束銷毀。注意哦RunLoop在子線程中不是自動(dòng)創(chuàng)建的。在NSRunLoop進(jìn)入休眠狀態(tài)前會(huì)把該進(jìn)程中的東西釋放(autoreleasepool),第二次喚醒的時(shí)候再把數(shù)據(jù)添加進(jìn)池子。
Timer
#pragma mark 定時(shí)器
-(void)timer{
//調(diào)用了scheduledTimer返回的定時(shí)器,已經(jīng)自動(dòng)被添加到當(dāng)前的runLoop中,而且是NSDefaultRunLoopMode 但是仍然可以通過(guò)addtimer的方式修改他的runloop
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//選擇在一個(gè)模式(mode)下運(yùn)行,要想切換就必須先退出當(dāng)前模式,然后再去重開(kāi)另外一個(gè)模式。一個(gè)模式下只能執(zhí)行當(dāng)面模式下的狀態(tài)
//添加定時(shí)器只在NSDefaultRunLoopMode(空閑)下可以使用 進(jìn)入其他模式后timer就不能工作了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//添加定時(shí)器只在UITrackingRunLoopMode(滑動(dòng))下可以使用 進(jìn)入其他模式后timer就不能工作了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//定時(shí)器會(huì)表跑在標(biāo)記為NSRunLoopCommonModes的模式下 標(biāo)記有UITrackingRunLoopMode(scrollerview滾動(dòng))和NSDefaultRunLoopMode(空閑狀態(tài)下)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
Source
Observers
#pragma mark Observer
-(void)observers{
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"---------監(jiān)聽(tīng)狀態(tài)改變----%zd",activity);
});
//添加觀察者:監(jiān)聽(tīng)runloop的狀態(tài)
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//釋放observ
CFRelease(observer);
//Core foundati內(nèi)Create Copy Retain 都會(huì)讓計(jì)數(shù)器+1 都要釋放
}
PerformSelecter
- 讓某個(gè)對(duì)象在執(zhí)行performSelector的時(shí)候一定要處于某種情況下 這句話在@selector的方法執(zhí)行完的時(shí)候出于消亡狀態(tài)。
- 當(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ì)失效。
[object performSelector:@selector(method) withObject:nil afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
- 當(dāng)調(diào)用 performSelector:onThread: 時(shí),實(shí)際上其會(huì)創(chuàng)建一個(gè) Timer 加到對(duì)應(yīng)的線程去,同樣的,如果對(duì)應(yīng)線程沒(méi)有 RunLoop 該方法也會(huì)失效。
[self performSelector:@selector(method) onThread:Somethread withObject:nil waitUntilDone:NO];
GCD
- 當(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 處理的。
常駐線程
- 概念:讓一個(gè)子線程不進(jìn)入消亡狀態(tài),讓他一直處于runloop狀態(tài) 等待其他線程發(fā)來(lái)消息 處理時(shí)間。
- 做法:在該線程中添加一個(gè)mode并且run起來(lái)該NSRunLoop
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建線程
self.thread = [[WXZThread alloc] initWithTarget:self selector:@selector(run) object:nil];
//開(kāi)啟線程
[self.thread start];
}
- (void)run
{
NSLog(@"----------run----%@", [NSThread currentThread]);
//保證線程永遠(yuǎn)都在運(yùn)行狀態(tài),并且開(kāi)始在NSDefaultRunLoopMode下等待處理[NSPort port]
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//開(kāi)始啟動(dòng)循環(huán)處理self.thread線程內(nèi)的事件
[[NSRunLoop currentRunLoop] run];
//后面的都不是self.thread線程的事情 所以沒(méi)有被打印 因?yàn)椴粫?huì)走到這一步 上面的一直在跑圈監(jiān)聽(tīng)self.thread的線程
NSLog(@"----------我沒(méi)有被打印---------");
}
//當(dāng)外界給這個(gè)正在RunLoo的線程一個(gè)外力時(shí),也能夠發(fā)送消息給test。因?yàn)閟elf.thread線程沒(méi)有消亡(RunLoop即使休眠了也會(huì)因?yàn)閟elf.thread里有mode而不會(huì)消亡)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//selector也是一個(gè)source
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test
{
NSLog(@"----------test----%@", [NSThread currentThread]);
}
在什么情況下可以使用runloop
- 常駐線程 概念:讓一個(gè)子線程不進(jìn)入消亡狀態(tài),讓他一直處于runloop狀態(tài) 等待其他線程發(fā)來(lái)消息 處理時(shí)間。做法:在該線程中添加一個(gè)mode并且run起來(lái)該NSRunLoop
- 在子線程中開(kāi)啟定時(shí)器NSTimer
- 在子線程中進(jìn)行一些長(zhǎng)期監(jiān)控 在什么情況下使用:長(zhǎng)時(shí)間的耗時(shí)操作 比如socket
- 讓某些事情在特定的mode下實(shí)行
- 可以添加observers監(jiān)聽(tīng)runloop的狀態(tài) 比如在點(diǎn)擊事件前先做一些事情
一些面試題
runloop 和線程的關(guān)系:
- 主線程的run loop默認(rèn)是啟動(dòng)的。
iOS的應(yīng)用程序里面,程序啟動(dòng)后會(huì)有一個(gè)如下的main()函數(shù)
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}}
重點(diǎn)是UIApplicationMain()函數(shù),這個(gè)方法會(huì)為main thread設(shè)置一個(gè)NSRunLoop對(duì)象,這就解釋了:為什么我們的應(yīng)用可以在無(wú)人操作的時(shí)候休息,需要讓它干活的時(shí)候又能立馬響應(yīng)。
對(duì)其它線程來(lái)說(shuō),run loop默認(rèn)是沒(méi)有啟動(dòng)的,如果你需要更多的線程交互則可以手動(dòng)配置和啟動(dòng),如果線程只是去執(zhí)行一個(gè)長(zhǎng)時(shí)間的已確定的任務(wù)則不需要。
在任何一個(gè) Cocoa 程序的線程中,都可以通過(guò)以下代碼來(lái)獲取到當(dāng)前線程的 run loop 。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
runloop的mode作用是什么?
1.model 主要是用來(lái)指定事件在運(yùn)行循環(huán)中的優(yōu)先級(jí)的,分為:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默認(rèn),空閑狀態(tài)
UITrackingRunLoopMode:ScrollView滑動(dòng)時(shí)
UIInitializationRunLoopMode:?jiǎn)?dòng)時(shí)
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
2.蘋(píng)果公開(kāi)提供的 Mode 有兩個(gè):
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
- NSRunLoopCommonModes(kCFRunLoopCommonModes)
以+ scheduledTimerWithTimeInterval...的方式觸發(fā)的timer,在滑動(dòng)頁(yè)面上的列表時(shí),timer會(huì)暫定回調(diào),為什么?如何解決?
RunLoop只能運(yùn)行在一種mode下,如果要換mode,當(dāng)前的loop也需要停下重啟成新的。利用這個(gè)機(jī)制,ScrollView滾動(dòng)過(guò)程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode會(huì)切換到UITrackingRunLoopMode來(lái)保證ScrollView的流暢滑動(dòng):只能在NSDefaultRunLoopMode模式下處理的事件會(huì)影響scrllView的滑動(dòng)。
如果我們把一個(gè)NSTimer對(duì)象NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)中的時(shí)候, ScrollView滾動(dòng)過(guò)程中會(huì)因?yàn)閙ode的切換,而導(dǎo)致NSTimer將不再被調(diào)度。
同時(shí)因?yàn)閙ode還是可定制的,所以:Timer計(jì)時(shí)會(huì)被scrollView的滑動(dòng)影響的問(wèn)題可以通過(guò)將timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)來(lái)解決。
猜想runloop內(nèi)部是如何實(shí)現(xiàn)的?
一般來(lái)講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線程能隨時(shí)處理事件但并不退出,通常的代碼邏輯 是這樣的:
int main(int argc, char * argv[]) {
//程序一直運(yùn)行狀態(tài)
while(AppIsRunning) {
//睡眠狀態(tài),等待喚醒事件
id whoWakesMe = SleepForWakingUp();
//得到喚醒事件
id event = GetEvent(whoWakesMe);
//開(kāi)始處理事件
HandleEvent(event);}
return0;
}