run loop簡介
run loop是一個關聯線程的基礎設施。run loop是一個事件處理循環,主要用於安排工作和接收事件。它可以讓線程在有工作的時
候繁忙,沒有工作的時候休眠。如果沒有run loop的話,你需要創建while或者for循環來不斷監聽事件,但這樣性能低下。
每個線程都有一個run loop,你不必手動創建run loop,可以通過CFRunLoopGetCurrent()或者[NSRunLoop currentRunLoop]獲
取當前線程的run loop。主線程默認開啓run loop並接收用戶事件。當你點擊按鈕時,此時對應的點擊事件會觸發並把事件分配到
run loop並處理事件對應的Handler。
run loop事件分爲兩種,一種是輸入源,一種是定時器源。輸入源又分爲Custom輸入源和Port輸入源。Custom輸入源是客服端發
送信號,Port輸入源是系統發送信號。定時器源是週期性發送信號的源,它發送同步事件。輸入源發送異步事件,通常來自其它線
程或者其它應用。
run loop mode是輸入源和定時器源的集合。你可以創建自己的mode(只需要傳遞一個字符串)或者使用系統提供的mode。只有
run loop運行在特定的mode下,該mode下的源纔會被監視並處理事件。
一下是系統提供的mode。
大多數情況下使用的是Default mode,也可以是Common mode,它包含Default mode、Model mode、Event tracking mode。
主線程是默認開啓run loop,其它線程需要手動開啓run loop。所以在其它線程使用NSTimer,需要把NSTimer添加到線程的run loop。
- (void)myThread2 {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireTimer:) userInfo:nil repeats:YES];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:timer forMode:NSDefaultRunLoopMode];
[runloop run];
}
run loop會自動創建自動釋放池。每次run loop休眠時會釋放自動釋放池的對象。
cocoa的selector源會自動加入到當前的run loop上,selector執行完畢後會自動從run loop移除。在其它線程中,你需要手動開啓
run loop,否則selector不會執行。
- (void)myThread3 {
[self performSelector:@selector(myMethod) withObject:nil afterDelay:0];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop run];
}
run loop的簡單應用
- (void)myThread {
NSLog(@"線成開始");
_runloop = CFRunLoopGetCurrent();
[NSRunLoop currentRunLoop];
CFRunLoopSourceContext context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL, schedule, cancel, perform};
_source = CFRunLoopSourceCreate(NULL, 0, &context);
CFRunLoopAddSource(_runloop, _source, kCFRunLoopDefaultMode);
CFRunLoopObserverContext observerContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
_observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, true, 0, observeCallback, &observerContext);
CFRunLoopAddObserver(_runloop, _observer, kCFRunLoopDefaultMode);
BOOL done = NO;
do {
CFRunLoopRunResult result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
switch (result) {
case kCFRunLoopRunFinished:
NSLog(@"kCFRunLoopRunFinished");
case kCFRunLoopRunStopped:
NSLog(@"kCFRunLoopRunStopped");
done = YES;
break;
case kCFRunLoopRunTimedOut:
NSLog(@"kCFRunLoopRunTimedOut");
break;
case kCFRunLoopRunHandledSource:
NSLog(@"kCFRunLoopRunHandledSource");
break;
}
} while (!done);
NSLog(@"線程結束");
}
這裏CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false)的10代表run loop運行的時間(單位爲秒)。當明確地停止run
loop或者沒有源在run loop時,run loop會退出而且線程結束。
CFRunLoopSourceContext的最後一個參數是一個C函數指針,用於處理事件的。info指針是自定義參數,用於傳遞給註冊函數的
info參數。CFRunLoopSourceContext的結構如下:
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
以下是註冊函數的實現
void perform(void *info) {
ViewController *vc = (__bridge ViewController *)info;
NSLog(@"執行開始-info=%@", vc);
sleep(1);
NSLog(@"執行結束-info=%@", vc);
}
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
ViewController *vc = (__bridge ViewController *)info;
NSLog(@"schedule info=%@,mode=%@", vc, mode);
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
ViewController *vc = (__bridge ViewController *)info;
NSLog(@"cancel info=%@,mode=%@", vc, mode);
}
schedule函數會在添加源的時候執行。perform函數會在事件到來時執行。cancel函數會在源失效時執行。
向源發送信號並喚醒run loop,觸發perform函數。
- (IBAction)clickButton:(id)sender {
if (CFRunLoopIsWaiting(_runloop)) {
if (CFRunLoopSourceIsValid(_source)) {
NSLog(@"發送信號");
CFRunLoopSourceSignal(_source);
CFRunLoopWakeUp(_runloop);
} else {
NSLog(@"CFRunLoopSourceInvalidate");
}
} else {
NSLog(@"CFRunLoopIsWaiting");
}
}
讓源失效,觸發cancel函數。
- (IBAction)clickCancelBtn:(id)sender {
if (CFRunLoopIsWaiting(_runloop)) {
NSLog(@"取消源");
CFRunLoopSourceInvalidate(_source);
}
}
手動停止run loop。
- (IBAction)clickStopButton:(id)sender {
if (CFRunLoopIsWaiting(_runloop)) {
NSLog(@"停止RunLoop");
CFRunLoopStop(_runloop);
}
}
移除源。
- (IBAction)clickRemoveButton:(id)sender {
if (CFRunLoopIsWaiting(_runloop)) {
NSLog(@"移除源");
CFRunLoopRemoveSource(_runloop, _source, kCFRunLoopDefaultMode);
}
}
添加run loop觀察者。
CFRunLoopObserverContext observerContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
_observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, true, 0, observeCallback, &observerContext);
CFRunLoopAddObserver(_runloop, _observer, kCFRunLoopDefaultMode);
observeCallBack是C函數指針,用於處理觀察事件。info指針是自定義參數,用於傳遞給註冊函數的info參數。
void observeCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
每次運行run loop,線程的run loop會自動處理之前未處理的信息,並通知相關觀察者。具體的順序如下:
1. 通知觀察者run loop已經啓動。
2. 通知觀察者任何即將要開始的定時器。
3. 通知觀察者任何即將啓動的非基於端口的源。
4. 啓動任何準備好的非基於端口的源。
5. 如果基於端口的源準備好並處於等待狀態,立即啓動;並進入步驟9。
6. 通知觀察者線程進入休眠。
7. 將線程置於休眠知道任一下面事件發生:
• 某一事件到達基於端口的源。
• 定時器啓動。
• run loop設置的時間已經超時。
• run loop被顯示喚醒。
8. 通知觀察者線程將被喚醒。
9. 處理未處理的事件。
• 如果用戶定義的定時器啓動,處理定時器事件並重啓run loop。進入步驟2。
• 如果輸入源啓動,傳遞相應的消息。
• 如果run loop被顯式喚醒並且時間還沒超時,重啓run loop。進入步驟2。
10. 通知觀察者run loop結束。
當run loop還有源但事件滅有到來時,run loop進入休眠等待狀態。一下是打印的log。
2017-10-25 16:03:20.434434+0800 aa[5027:3503969] 發送信號
2017-10-25 16:03:20.434853+0800 aa[5027:3504031] kCFRunLoopAfterWaiting
2017-10-25 16:03:20.434914+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:20.434948+0800 aa[5027:3504031] kCFRunLoopBeforeSources
2017-10-25 16:03:20.435070+0800 aa[5027:3504031] 執行開始-info=<ViewController: 0x102b0eb90>
2017-10-25 16:03:21.436837+0800 aa[5027:3504031] 執行結束-info=<ViewController: 0x102b0eb90>
2017-10-25 16:03:21.436988+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:21.437047+0800 aa[5027:3504031] kCFRunLoopBeforeSources
2017-10-25 16:03:21.437096+0800 aa[5027:3504031] kCFRunLoopBeforeWaiting
2017-10-25 16:03:22.467261+0800 aa[5027:3503969] 發送信號
2017-10-25 16:03:22.467683+0800 aa[5027:3504031] kCFRunLoopAfterWaiting
2017-10-25 16:03:22.467715+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:22.467733+0800 aa[5027:3504031] kCFRunLoopBeforeSources
run loop的使用環境
1. 需要一個輔助線程來監聽和處理事件,但需要消耗較低的性能。
2. 使用自定義源或者端口源來和其它線程通訊。
3. 在子線程使用定時器。
4. 在子線程使用任何performSelector方法。
5. 保持線程週期性執行任務。
儘量避免手動停止run loop,應該讓run loop在一定時間內退出或者把所有源移除後自動退出。