對(duì)RunLoop運(yùn)行機(jī)制不熟悉的可以先看我的這篇文章:深入理解RunLoop
我們都知道,iOS的tableView能做到滑動(dòng)很平滑,一部分是依賴于runloop的mode的切換。當(dāng)系統(tǒng)檢測(cè)到有scrollerview滑動(dòng)時(shí),系統(tǒng)就會(huì)將當(dāng)前進(jìn)程的主線程切換到UITrackingRunLoopMode,直到滑動(dòng)結(jié)束,又會(huì)切換到NSDefaultRunLoopMode。這個(gè)過(guò)程聽(tīng)起來(lái)很奇妙,那么他是怎么做到的呢,我們能不能在需要的時(shí)候也這么做呢?
答案是肯定的,我們可以模擬這個(gè)過(guò)程,我的思路是這樣的:由于主線程不是一個(gè)runloop干凈的線程,所以我們另外啟動(dòng)一個(gè)子線程,
//使用GCD啟動(dòng)一個(gè)子線程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
為了保證線程不退出,我們添加一個(gè)timer, 讓其在下NSDefaultRunLoopMode模式下運(yùn)行,按正常情況下,下面的代碼會(huì)在一直打印“timer1 fired”,該子線程會(huì)永遠(yuǎn)運(yùn)行在NSDefaultRunLoopMode模式下。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//timer1運(yùn)行在default mode
NSTimer *timer1 = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer1 fired");
}];
[[NSRunLoop currentRunLoop] addTimer:timer1 forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
但是主線程是可以切換mode的,那么這樣肯定不行,我們只有改變r(jià)unloop的啟動(dòng)方式,只有用更好控制的runMode: beforeDate:這個(gè)函數(shù),大家知道這個(gè)函數(shù)運(yùn)行起來(lái)的runloop,會(huì)在指定的mode下運(yùn)行一次便會(huì)退出,但是由于我們添加的是timer,所有不會(huì)主動(dòng)退出,但是我們可以采用CFRunLoopStop()主動(dòng)退出,只有runloop能退出,我們才又機(jī)會(huì)切換mode,下面是實(shí)現(xiàn)代碼
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//timer1運(yùn)行在default mode
NSTimer *timer1 = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer1 fired");
}];
[[NSRunLoop currentRunLoop] addTimer:timer1 forMode:NSDefaultRunLoopMode];
self.currentMode = kCFRunLoopDefaultMode;
while (1) {
[[NSRunLoop currentRunLoop] runMode:self.currentMode beforeDate:[NSDate distantFuture]];
}
});
這樣寫就可以通過(guò)控制currentMode這個(gè)全局變量來(lái)控制runloop下次運(yùn)行的mode。我們模擬在touchbegan的時(shí)候切換到UITrackingRunLoopMode,touchend的時(shí)候又切換回NSDefaultRunLoopMode,為了顯示runloop確實(shí)切換到了UITrackingRunLoopMode,我們啟動(dòng)另一個(gè)timer讓其運(yùn)行在UITrackingRunLoopMode,看打印日志就可以了。代碼實(shí)現(xiàn)是這樣的:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.rl = CFRunLoopGetCurrent();
//timer1運(yùn)行在default mode
NSTimer *timer1 = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer1 fired");
}];
[[NSRunLoop currentRunLoop] addTimer:timer1 forMode:NSDefaultRunLoopMode];
//timer2運(yùn)行在track Mode
NSTimer *timer2 = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer2 fired");
}];
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:UITrackingRunLoopMode];
//指定當(dāng)前運(yùn)行mode
self.currentMode = NSDefaultRunLoopMode;
while (1) {
[[NSRunLoop currentRunLoop] runMode:self.currentMode beforeDate:[NSDate distantFuture]];
}
});
- (void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touch began")
//touchbegan 切換成track mode
self.currentMode = UITrackingRunLoopMode;
CFRunLoopStop(self.rl);
}
- (void) touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touch end");
//touchend 切換成kCFRunLoopDefaultMode
self.currentMode = kCFRunLoopDefaultMode;
CFRunLoopStop(self.rl);
}
代碼運(yùn)行起來(lái)后,就可以看到,在觸摸之前只有timer1打印,在手指觸摸時(shí)之后timer2打印
2020-06-05 14:48:39.932264+0800 [18468:8621222] timer1 fired
2020-06-05 14:48:40.932321+0800 [18468:8621222] timer1 fired
2020-06-05 14:48:41.934566+0800 [18468:8621222] timer1 fired
2020-06-05 14:48:42.934567+0800 [18468:8621222] timer1 fired
2020-06-05 14:48:43.344774+0800 [18468:8620979] touch began
2020-06-05 14:48:43.345429+0800 [18468:8621222] timer2 fired
2020-06-05 14:48:43.934576+0800 [18468:8621222] timer2 fired
2020-06-05 14:48:44.929843+0800 [18468:8621222] timer2 fired
2020-06-05 14:48:45.294261+0800 [18468:8620979] touch end
2020-06-05 14:48:45.294846+0800 [18468:8621222] timer1 fired
通過(guò)這樣我們就模擬了系統(tǒng)的mode切換過(guò)程,也順便知道了,為什么NSTimer需要設(shè)置在NSRunLoopCommonModes模式下運(yùn)行。