RunLoop 是什么
RunLoop 是一個(gè)事件處理的循環(huán)畜挨,目的是有工作要做時(shí)讓線程忙碌,沒有工作要做時(shí)讓線程進(jìn)入睡眠狀態(tài)萍启。
Cocoa 和 Core Foundation 兩個(gè)層面都提供了 RunLoop 的對(duì)象驶乾,即 NSRunLoop 和 CFRunLoop。
RunLoop 與線程是一對(duì)一的關(guān)系喊暖。不能手動(dòng)創(chuàng)建 RunLoop,只能 [NSRunLoop currentRunLoop] 或 CFRunLoopGetCurrent()撕瞧,調(diào)用這兩個(gè)方法時(shí)當(dāng)沒有 RunLoop 會(huì)自動(dòng)創(chuàng)建一個(gè)并返回陵叽。
模式
我們常用到的三種模式:
- NSDefaultRunLoopMode (kCFRunLoopDefaultMode):默認(rèn)模式。
- UITrackingRunLoopMode:當(dāng)拖動(dòng) UIScrollView 觸發(fā)的模式丛版。
- NSRunLoopCommonModes (kCFRunLoopCommonModes) 占位模式巩掺,相當(dāng)于前面兩種模式都添加進(jìn)去了。
一個(gè) Runloop 可以有多個(gè)模式, Runloop 執(zhí)行其中一種模式的時(shí)候页畦,另外的模式不會(huì)執(zhí)行胖替。
兩種 Sources
Input Sources 和 Timer Sources。
Sources 的作用是使 RunLoop 從睡眠狀態(tài)轉(zhuǎn)化到工作狀態(tài)豫缨。
Input Sources 是指 Port-Based Sources独令、Custom Input Sources、Cocoa Perform Selector Sources好芭。
Port-Based Sources 的類有:
NSPort
NSMachPort
...
Custom Input Sources 通常用到 CFRunLoopSourceRef
Cocoa Perform Selector Sources 有以下的方法:
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
Timer Sources 就是指 NSTimer燃箭。
Observers
可以添加觀察者去觀察 RunLoop 的狀態(tài)的變化,狀態(tài)有以下幾種
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
應(yīng)用例子
子線程里創(chuàng)建一個(gè)定時(shí)執(zhí)行的跑圈
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static int count = 0;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"執(zhí)行定時(shí)器任務(wù)");
count ++;
if (count >= 5) {
[timer invalidate];
}
}];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(@"runLoop 開啟");
[runLoop run];
NSLog(@"runLoop 退出");
});
運(yùn)行結(jié)果:
runLoop 開啟
執(zhí)行定時(shí)器任務(wù)
執(zhí)行定時(shí)器任務(wù)
執(zhí)行定時(shí)器任務(wù)
執(zhí)行定時(shí)器任務(wù)
執(zhí)行定時(shí)器任務(wù)
runLoop 退出
注意當(dāng) Timer 不失效舍败,那么 RunLoop 永遠(yuǎn)不會(huì)退出招狸。
主線程中使用 [NSTimer scheduledTimerWith...] 并不需要 RunLoop 去 addTimer 是因?yàn)榇朔椒J(rèn)在主線程的 RunLoop 添加該 Timer。
子線程里創(chuàng)建一個(gè)監(jiān)聽 port 的跑圈
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.port = [NSPort port];
self.port.delegate = self;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:self.port forMode:NSDefaultRunLoopMode];
NSLog(@"開始");
[runLoop run];
NSLog(@"結(jié)束");
});
}
- (void)handlePortMessage:(NSPortMessage *)message {
NSLog(@"handlePortMessage:%@", message);
[[NSRunLoop currentRunLoop] removePort:self.port forMode:NSDefaultRunLoopMode];
}
- (IBAction)clickSendButton:(id)sender {
[self.port sendBeforeDate:[NSDate date] components:nil from:nil reserved:0];
}
移除 Port 即可退出 RunLoop邻薯。
解決 Timer 添加到主線程時(shí)的問題
因?yàn)?Timer 默認(rèn)是以 NSDefaultRunLoopMode 添加到 RunLoop 的裙戏,當(dāng)拖動(dòng) UIScrollView 時(shí),切換到了 UITrackingRunLoopMode 厕诡,所以會(huì)導(dǎo)致Timer 的任務(wù)被延遲執(zhí)行了累榜,解決這個(gè)問題可以把 Timer 添加到 NSRunLoopCommonModes 模式。但是可能會(huì)引起另外一個(gè)問題灵嫌,如果 Timer 中的任務(wù)比較耗時(shí)信柿,會(huì)影響拖動(dòng) UIScrollView 的效果冀偶,這時(shí)可以把 Timer 在子線程中運(yùn)行。
把加載多張圖片的耗時(shí)操作分散到多次循環(huán)中
之前發(fā)現(xiàn)一個(gè)利用 RunLoop 優(yōu)化的開源代碼 RunLoopWorkDistribution渔嚷,運(yùn)行效果自行查看開源項(xiàng)目 https://github.com/diwu/RunLoopWorkDistribution 。
沒優(yōu)化前的主要問題是:多張圖片的加載任務(wù)會(huì)是需要在一次循環(huán)中執(zhí)行完的稠曼,所以會(huì)比較耗時(shí)形病,造成界面的卡頓。解決技巧是把每一張圖片分散在每一次循環(huán)中霞幅,用一個(gè)任務(wù)隊(duì)列依次加載圖片漠吻。為了觸發(fā)多次循環(huán),添加一個(gè) Timer 去觸發(fā)司恳。
檢測卡頓
添加一個(gè)觀察者途乃,觀察 RunLoop 的 處理事件前的狀態(tài)和睡眠前的狀態(tài),兩者之間的時(shí)間差達(dá)到一定值扔傅,并且連續(xù)五次以上耍共,就判定為卡頓。
參考開源代碼 https://github.com/suifengqjn/PerformanceMonitor
處理一些交互畫面問題
有時(shí)會(huì)發(fā)生一些奇怪的交互畫面問題猎塞,原因是兩個(gè) UI 更新都在 RunLoop 的同一個(gè)循環(huán)里了试读,解決方法是把其中一個(gè)放到下一個(gè)循環(huán)當(dāng)中。最便捷的就是調(diào)用 performSelector:withObject:afterDelay:
還有其他應(yīng)用例子待添加荠耽。