什么是RunLoop?
從字面上來看是運(yùn)行循環(huán)的意思.
內(nèi)部就是一個(gè)do{}while循環(huán),在這個(gè)循環(huán)里內(nèi)部不斷的處理各種任務(wù)(比如:source/timer/Observer)
RunLoop的存在其實(shí)就是為線程而存在的.線程的作用就是執(zhí)行一個(gè)特定的任務(wù),但是默認(rèn)情況下線程執(zhí)行完任務(wù)后就不能再次執(zhí)行任務(wù),這是因?yàn)槟J(rèn)情況下線程是沒有開啟RunLoop的.如果開啟RunLoop之后,線程執(zhí)行完任務(wù)之后,會(huì)一直等待,直到再次接受到任務(wù),接續(xù)執(zhí)行任務(wù).線程銷毀前,會(huì)先釋放這個(gè)線程所對(duì)應(yīng)的RunLoop.
RunLoop基本作用
保持程序的持續(xù)運(yùn)行,保持線程的持續(xù)運(yùn)行.
處理App中的各種事件(比如觸摸事件,定時(shí)器事件,Selector事件)
節(jié)省CPU資源,提高程序性能:該做事時(shí)做事,該休息時(shí)休息
RunLoop對(duì)象
ios中有2套API來訪問和使用RunLoop
一套是Fundation(純OC的)框架中的
NSRunLoop
// 獲得當(dāng)前線程的RunLoop對(duì)象[NSRunLoop currentRunLoop];// 獲得主線程的RunLoop對(duì)象[NSRunLoop mainRunLoop];
一套是Core Fundation(純C語言的)框架中的
CFRunLoopRef
// 獲得當(dāng)前線程的RunLoop對(duì)象CFRunLoopGetCurrent();// 獲得主線程的RunLoop對(duì)象CFRunLoopGetMain();
NSRunLoo和CFRunLoopRef都代表著RunLoop對(duì)象.NSRunLoop是基于CFRunLoopRef的一層OC包裝
RunLoop與線程
每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象
主線程的Runloop系統(tǒng)已經(jīng)自動(dòng)創(chuàng)建好了,子線程的RunLoop需要手動(dòng)創(chuàng)建
RunLoop在第一次獲取時(shí)由系統(tǒng)自動(dòng)創(chuàng)建,在線程結(jié)束時(shí)銷毀
如果想給子線程創(chuàng)建RunLoop,不能直接alloc&init,只要調(diào)用獲取當(dāng)前線程RunLoop方法即可,系統(tǒng)會(huì)自動(dòng)放回當(dāng)前線程的RunLoop,如果當(dāng)前線程沒有RunLoop,系統(tǒng)會(huì)自動(dòng)創(chuàng)建.
RunLoop相關(guān)類
Core Fundation中關(guān)于RunLoop的5個(gè)類
CFRunLoopRef: RunLoop對(duì)象
CFRunLoopModeRef: RunLoop運(yùn)行模式.
CFRunLoopSoruceRef: 事件源(輸入源)
CFRunLoopTimerRef:基于時(shí)間的觸發(fā)器.
CFRunLoopObserverRef: 觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop運(yùn)行模式
一個(gè)RunLoop對(duì)象包含若干個(gè)Mode(模式),每個(gè)Mode又包含若干個(gè) source/Timer/Observer
RunLoop運(yùn)行時(shí),只能指定一個(gè)Mode, 這個(gè)Mode又稱之為CurrentMode,然后RunLoo就執(zhí)行CurrentMode中的source/Timer/Observer
如果需要切換Mode,只能退出RunLoop,再重新指定一個(gè)Mode進(jìn)入,這樣做是為了分隔開不同組的Source/Timer/Observer,讓其不受影響
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
NSDefaultRunLoopMode: App的默認(rèn)Mode,通常主線程實(shí)在這個(gè)模式下運(yùn)行
UITrackingRunLoopMode:界面跟蹤Mode,用于界面控件(ScrollView,tableView等等)追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode影響
UIInitializationRunLoopMode:在剛啟動(dòng)App是進(jìn)入的第一個(gè)Mode,啟動(dòng)完成后就不再使用
GSEventReceiveRunLoopMode:接收系統(tǒng)事件的內(nèi)部Mode,通常用不到
NSRunLoopCommonMode:這是一個(gè)占位的Mode,不是一種真正的Mode,(可以看成模式組,默認(rèn)情況下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)兩種模式.
CFRunLoopTimerRef
CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器
CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創(chuàng)建一個(gè)NSTimer定時(shí)器,默認(rèn)情況下NSTimer是不會(huì)執(zhí)行的,只有把NSTimer添加到RunLoop中,由RunLoop管理執(zhí)行
NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 在當(dāng)前線程中RunLoop添加一個(gè)timer, 并告訴runLoop, 這個(gè)timer只能在NSDefaultRunLoopMode模式下才能觸發(fā)
// runLoop會(huì)找到NSDefaultRunLoopMode,然后把timer添加NSDefaultRunLoopMode中的Timer數(shù)組中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//在當(dāng)前線程中RunLoop添加一個(gè)timer, 并告訴runLoop, 這個(gè)timer只能在NSRunLoopCommonModes模式下才能觸發(fā)
//runLoop會(huì)找到NSDefaultRunLoopMode和UITrackingRunLoopMode
//然后把timer添加NSDefaultRunLoopMode和UITrackingRunLoopMode中的Timer數(shù)組中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//利用此方法創(chuàng)建的NSTimer, 系統(tǒng)會(huì)自動(dòng)放入當(dāng)前線程中的currentRunLoop中,并且只能在NSDefaultRunLoop模式下才能觸發(fā)
NSTimer * timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
//雖然通過類方法scheduledTimerWithTimeInterval創(chuàng)建NSTimer,會(huì)自動(dòng)添加到NSDefaultRunLoopMode模式中
//但我們還是可以修改它的模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
CFRunLoopSoruceRef
按照官方文檔,source的分類:
Port-Based Sources:基于端口的事件源:監(jiān)聽程序響應(yīng)的端口,基于端口事件是由系統(tǒng)內(nèi)核自動(dòng)發(fā)送的.
Custom Input Sources: 自定義輸入源:監(jiān)聽自定義事件源,而自定義的輸入源是需要人工從其他線程發(fā)送
Cocoa Perfrom Selector Source: selector事件源
按照源碼函數(shù)調(diào)用棧,source的分類:
Source0:非基于Prot(端口)的,是用戶主動(dòng)觸發(fā)的事件
Source1:基于Prot(端口)的,通過內(nèi)核和其他線程相互發(fā)送消息
CFRunLoopObserverRef
CFRunLoopObserverRef:觀察者對(duì)象,可以監(jiān)聽RunLoop的狀態(tài)
RunLoop狀態(tài):
kCFRunLoopEntry 即將進(jìn)入runLoop
kCFRunLoopBeforeTimers 即將處理Timer
kCFRunLoopBeforeSources 即將處理source(事件源)
kCFRunLoopBeforeWaiting 即將進(jìn)入休眠
kCFRunLoopAfterWaiting 即將從休眠中醒來
kCFRunLoopExit 即將退出runLoop
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創(chuàng)建一個(gè)CFRunLoopObserverRef
/*第一個(gè)參數(shù): CFRunLoopObserverRef(觀察者)分配內(nèi)存空間方式第二個(gè)參數(shù): 監(jiān)聽那些狀態(tài) kCFRunLoopAllActivities(監(jiān)聽所有狀態(tài))第三個(gè)參數(shù): 是否每次都要監(jiān)聽第四個(gè)參數(shù): 優(yōu)先級(jí)第五個(gè)參數(shù): 回調(diào)函數(shù)*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES,0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// observer監(jiān)聽對(duì)象
//activity Runloop當(dāng)前狀態(tài)
});
/*第一個(gè)參數(shù): 為那個(gè)線程下的RunLoop添加CFRunLoopObserverRef(觀察者)第二個(gè)參數(shù): 需要添加的CFRunLoopObserverRef(觀察者)第三個(gè)參數(shù): 把監(jiān)聽添加到RunLoop那個(gè)模式中
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//記得內(nèi)存管理,因?yàn)镃ore Foundation不在ARC管理范圍內(nèi)
//帶有Create、Copy氏身、Retain等字眼的函數(shù)巍棱,創(chuàng)建出來的對(duì)象,都需要在最后做一次release
//銷毀對(duì)象函數(shù):CFRelease對(duì)象CFRelease(observer);
}
RunLoop處理邏輯
如果RunLoop中沒有Timer或source,RunLoop就會(huì)立刻退出
每次運(yùn)行RunLoop,RunLoop會(huì)自動(dòng)處理之前未處理的消息,并通知相關(guān)觀察者.具體順序如下:
1.通知觀察者RunLoop已經(jīng)啟動(dòng)
2.通知觀察者即將開始啟動(dòng)定時(shí)器
3.通知觀察者即將啟動(dòng)非基于端口的事件源
4.啟動(dòng)任何準(zhǔn)備好的非基于端口的事件源
5.如果基于端口的事件源準(zhǔn)備好并處于等待得狀態(tài),立即啟動(dòng).并進(jìn)入步驟9
6.通知觀察者線程進(jìn)入休眠
7.將線程置于休眠直到任意下面的事件發(fā)生:
某一事件到達(dá)基于端口的源
定時(shí)器啟動(dòng)
RunLoop設(shè)置的時(shí)間已經(jīng)超時(shí).(系統(tǒng)底層會(huì)給RunLoop設(shè)置一個(gè)超時(shí)時(shí)間,源碼中設(shè)置的是:9999999999.0)
RunLoop被手動(dòng)喚醒
8.通知觀察者線程將被喚醒.
9.處理未處理的事件
如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器事件并重啟RunLoop.進(jìn)入步驟2
如果事件源啟動(dòng),傳遞相應(yīng)的消息
如果RunLooop被顯示喚醒而且時(shí)間還沒超時(shí),重啟RunLoop.進(jìn)入步驟2
10.通知觀察者RunLoop結(jié)束.
如何讓子線程成為常駐線程(讓一個(gè)子線程不進(jìn)入消亡狀態(tài)蛋欣,等待其他線程發(fā)來消息航徙,處理其他事件)
(#)import "ViewControllerRunLoop.h"
@interface ViewControllerRunLoop ()
@property(nonatomic,strong)NSThread * thread;
@end
@implementation ViewControllerRunLoop
-(void)viewDidLoad {
//創(chuàng)建子線程執(zhí)行任務(wù) self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [self.thread start];
}
-(void)run {
NSLog(@"跑起來");
//默認(rèn)情況下,子線程是不會(huì)常駐的
//只有子線程中runloop啟動(dòng),并且runloop中有source或timer,才會(huì)常駐
//只有常駐線程才能再次執(zhí)行任務(wù),因?yàn)榫€程中有runloop來處理事件了
//子線程的runloop是需要手動(dòng)創(chuàng)建的, 并且需要手動(dòng)啟動(dòng)
NSRunLoop * rl = [NSRunLoop currentRunLoop];
//如果子線程的runloop沒有 source / timer 的話, 哪么子線程的runloop會(huì)立即關(guān)閉
//在runLoop中添加一個(gè)timer
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector: @selector(timerRun) userInfo:nil repeats:YES];
//啟動(dòng)runloop [rl run];
//如果線程成為了常駐線程,你會(huì)發(fā)現(xiàn),不會(huì)執(zhí)行到這行代碼
//也就是說這個(gè)方法不會(huì)執(zhí)行完,
NSLog(@"end");
}
-(void)timerRun{
NSLog(@"%s",__func__);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 讓子線程再次執(zhí)行任務(wù)
[self performSelector:@selector(againRun) onThread:self.thread withObject:nil waitUntilDone:NO];
}
-(void)againRun{
NSLog(@"再次跑起來");
}
@end