- 什么是RunLoop?
從字面理解,循環(huán)跑。你也可以叫它事件循環(huán),消息循環(huán)。本質(zhì)是一個(gè)do{}while(0),條件永遠(yuǎn)為false的死循環(huán)。
- RunLoop和線程的關(guān)系?
1.每條線程都有與之對(duì)應(yīng)的runLoop。
2.主線程默認(rèn)是開啟的,子線程需要自己手動(dòng)開啟。
3.runLoop在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束時(shí)銷毀。
- RunLoop有什么用?
1.一般情況下,線程執(zhí)行完當(dāng)前任務(wù)就會(huì)銷毀,下次要使用又的重新創(chuàng)建。
2.當(dāng)我們開辟一條新的線程執(zhí)行任務(wù)的時(shí)候,是要耗費(fèi)cpu性能的。
3.runLoop可以讓你創(chuàng)建的線程不銷毀,當(dāng)你任務(wù)執(zhí)行完成后,它會(huì)在后臺(tái)休眠,保持程序運(yùn)行,當(dāng)你有新的任務(wù)需要執(zhí)行的時(shí)候,它會(huì)被系統(tǒng)喚醒,繼續(xù)執(zhí)行任務(wù),從而節(jié)約系統(tǒng)資源。
- 程序中哪里使用了?
不知道大家想過沒有,當(dāng)我們的app運(yùn)行之后,為什么能一直保持程序運(yùn)行,接收處理用戶的點(diǎn)擊,觸摸等事件?
我們來看看下面的代碼:
是不是很眼熟?沒錯(cuò),這就是程序的入口main方法,做了下小小修改。
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"--->0");
int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"--->1");
return result;
}
}
運(yùn)行之后你會(huì)發(fā)現(xiàn)--->1不會(huì)打印。因?yàn)閁IApplicationMain內(nèi)部開啟了個(gè)runLoop,也就是個(gè)死循環(huán),所以--->1永遠(yuǎn)都不會(huì)打印。
- 上面簡單介紹了RunLoop基本概念,下面我們來看看怎么創(chuàng)建使用。
- runLoop是不能自己創(chuàng)建的,只能使用系統(tǒng)方法獲取。
- apple為我們操作使用runLoop提供了下面2個(gè)對(duì)象。
1.CFRunLoopRef(Core Fundation框架)
基于c語言開發(fā),是開源的,線程安全的。
2.NSRunLoop(Fundation框架)
對(duì)CFRunLoopRef的簡單封裝,是面向?qū)ο蟮?,是線程不安全的。
- 獲取RunLoop
NSRunLoop:
[NSRunLoop mainRunLoop]; // 獲取主線程runLoop
[NSRunLoop currentRunLoop]; // 獲取當(dāng)前線程runLoop
CFRunLoopRef:
CFRunLoopGetMain(); // 獲取主線程runLoop
CFRunLoopGetCurrent(); // 獲取當(dāng)前線程runLoop
- RunLoop相關(guān)的類
- CFRunLoopRef
- CFRunLoopModelRef
- CFRunLoopSourceRef
- CFRunLoopTimeRef
- CFRunLoopObserverRef

1.一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer。
2.每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode,這個(gè)Mode被稱作 CurrentMode。
3.如果需要切換 Mode,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
CFRunLoopModelRef
runLoop運(yùn)行模式,系統(tǒng)提供了5種模式。
kCFRunLoopDefaultMode //App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode //界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
UIInitializationRunLoopMode // 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
GSEventReceiveRunLoopMode // 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
kCFRunLoopCommonModes //這是一個(gè)占位用的Mode,不是一種真正的Mode
CFRunLoopSourceRef
事件產(chǎn)生的地方,既事件源。
Source有2種:
1.source0
非基于Port的 ,用于用戶主動(dòng)觸發(fā)事件(按鈕點(diǎn)擊或觸摸之類)
2.source1
基于Port的,通過內(nèi)核和其它線程相互發(fā)送消息,能主動(dòng)喚醒runLoop的線程。
CFRunLoopTimeRef
基于時(shí)間的觸發(fā)器,當(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
觀察者。當(dāng)runLoop狀態(tài)發(fā)生改變,觀察者就能通過回調(diào)接收這個(gè)變化。
可監(jiān)測狀態(tài)如下:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
CFRunLoopObserverRef ref = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop進(jìn)入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要處理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要處理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒來了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), ref, kCFRunLoopCommonModes);
CFRelease(ref);
- RunLoop退出
1.線程銷毀。
2.當(dāng)mode中time,source都為空的時(shí)候,runLoop會(huì)立刻退出。
3.設(shè)置runLoop結(jié)束時(shí)間。
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>
- 自動(dòng)釋放池
主線程中會(huì)自動(dòng)創(chuàng)建自動(dòng)釋放池。
子線程中調(diào)用runLoop需手動(dòng)創(chuàng)建自動(dòng)釋放池。
子線程中才需要加入autoreleasepool
@autoreleasepool {
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
- runLoop應(yīng)用
- 保持線程不被銷毀
默認(rèn)情況下,當(dāng)任務(wù)執(zhí)行完后,線程會(huì)被立刻銷毀,如果需要在執(zhí)行任務(wù),必須重新開啟一條線程,在那種需要重復(fù)創(chuàng)建線程完成任務(wù)的情況下,會(huì)造成cpu性能的損耗。
開啟一個(gè)線程,并持有該線程
@property (nonatomic, strong) NSThread *thread;
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
_thread = thread;
[thread start];
- (void)threadTest
{
NSLog(@"threadTest.......%@",[NSThread currentThread]);
}
點(diǎn)擊屏幕的時(shí)候在該線程下在調(diào)用上面的函數(shù),你會(huì)發(fā)現(xiàn)它沒有打印。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(threadTest) onThread:_thread withObject:nil waitUntilDone:NO];
}
// 雖然用strong強(qiáng)引用了thread,使得thread沒有被釋放,但是你會(huì)發(fā)現(xiàn),你還是無法喚起該線程來執(zhí)行任務(wù)。
我們將上面的threadTest方法修改成下面這樣,你會(huì)發(fā)現(xiàn)當(dāng)點(diǎn)擊屏幕的時(shí)候它會(huì)正常調(diào)用了。
- (void)threadTest
{
NSLog(@"threadTest.......%@",[NSThread currentThread]); // 注意,這句話要放在runLoop上面,否則就不會(huì)打印了,因?yàn)閞unLoop是個(gè)死循環(huán)嘛。
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
- timerWithTimeInterval
- (void)viewDidLoad {
[super viewDidLoad];
[NSTimer timerWithTimeInterval:1 target:self selector:@selector(myTimer) userInfo:nil repeats:YES];
}
- (void)myTimer
{
NSLog(@"myTimer.......%@",[NSThread currentThread]); // 不會(huì)打印
}
// 你會(huì)發(fā)現(xiàn)上面的方法不會(huì)調(diào)用,因?yàn)閠imerWithTimeInterval方法并沒有加入到runLoop中,所以不會(huì)執(zhí)行。
在timerWithTimeInterval下面加上:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
- scheduledTimerWithTimeInterval
- (void)viewDidLoad {
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(myTimer) userInfo:nil repeats:YES];
}
- (void)myTimer
{
NSLog(@"myTimer.......%@",[NSThread currentThread]); // 會(huì)打印
}
// 你會(huì)發(fā)現(xiàn)上面的方法會(huì)調(diào)用,因?yàn)閟cheduledTimerWithTimeInterval會(huì)自動(dòng)加入到主線程中,主線程中的runLoop已默認(rèn)開啟,所以會(huì)執(zhí)行。
如果我們?cè)谧泳€程中調(diào)用scheduledTimerWithTimeInterval方法會(huì)這樣?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(myTimer) userInfo:nil repeats:YES];
});
// 你會(huì)發(fā)現(xiàn)myTimer方法不會(huì)調(diào)用了。如果想要它在子線程中調(diào)用怎么辦?
在子線程中加入:
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
總結(jié):1. timerWithTimeInterval
不會(huì)自動(dòng)在主線程runLoop中運(yùn)行,需要手動(dòng)添加到runLoop中運(yùn)行。2.scheduledTimerWithTimeInterval
會(huì)自動(dòng)添加到主線程runLoop中運(yùn)行,但如果你在子線程中調(diào)用該方法,則需要手動(dòng)添加到子線程runLoop中,否則不會(huì)運(yùn)行。
- RunLoop內(nèi)部處理流程


1.通知觀察者 run loop 已經(jīng)啟動(dòng)
2.通知觀察者將要開始處理Timer事件
3.通知觀察者將要處理非基于端口的Source0
4.啟動(dòng)準(zhǔn)備好的Souecr0
5.如果基于端口的源Source1準(zhǔn)備好并處于等待狀態(tài),立即啟動(dòng):并進(jìn)入步驟9
6.通知觀察者線程進(jìn)入休眠
7.將線程置于休眠直到任一下面的事件發(fā)生
改:
(1)某一事件到達(dá)基于端口的源
(2)定時(shí)器啟動(dòng)
(3)Run loop 設(shè)置的時(shí)間已經(jīng)超時(shí)
(4)run loop 被顯式喚醒
8.通知觀察者線程將被喚醒
9.處理未處理的事件,跳回2
改:
(1)如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器事件并重啟 run loop。進(jìn)入步驟 2
(2)如果輸入源啟動(dòng),傳遞相應(yīng)的消息
(3)如果 run loop 被顯式喚醒而且時(shí)間還沒超時(shí),重啟 run loop。進(jìn)入步驟 2
10.通知觀察者run loop 結(jié)束
- 參考
http://www.cocoachina.com/ios/20150601/11970.html
http://www.itdecent.cn/p/b9426458fcf6