對RunLoop運行機制不熟悉的可以先看我的這篇文章:深入理解RunLoop
我們都知道酸舍,iOS的tableView能做到滑動很平滑,一部分是依賴于runloop的mode的切換。當系統(tǒng)檢測到有scrollerview滑動時饮睬,系統(tǒng)就會將當前進程的主線程切換到UITrackingRunLoopMode,直到滑動結(jié)束现恼,又會切換到NSDefaultRunLoopMode。這個過程聽起來很奇妙量九,那么他是怎么做到的呢,我們能不能在需要的時候也這么做呢颂碧?
答案是肯定的荠列,我們可以模擬這個過程,我的思路是這樣的:由于主線程不是一個runloop干凈的線程载城,所以我們另外啟動一個子線程肌似,
//使用GCD啟動一個子線程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
為了保證線程不退出,我們添加一個timer, 讓其在下NSDefaultRunLoopMode模式下運行,按正常情況下诉瓦,下面的代碼會在一直打印“timer1 fired”川队,該子線程會永遠運行在NSDefaultRunLoopMode模式下力细。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//timer1運行在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的,那么這樣肯定不行固额,我們只有改變runloop的啟動方式眠蚂,只有用更好控制的runMode: beforeDate:這個函數(shù),大家知道這個函數(shù)運行起來的runloop斗躏,會在指定的mode下運行一次便會退出逝慧,但是由于我們添加的是timer,所有不會主動退出啄糙,但是我們可以采用CFRunLoopStop()主動退出笛臣,只有runloop能退出,我們才又機會切換mode隧饼,下面是實現(xiàn)代碼
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//timer1運行在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]];
}
});
這樣寫就可以通過控制currentMode這個全局變量來控制runloop下次運行的mode沈堡。我們模擬在touchbegan的時候切換到UITrackingRunLoopMode,touchend的時候又切換回NSDefaultRunLoopMode燕雁,為了顯示runloop確實切換到了UITrackingRunLoopMode踱蛀,我們啟動另一個timer讓其運行在UITrackingRunLoopMode,看打印日志就可以了贵白。代碼實現(xiàn)是這樣的:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.rl = CFRunLoopGetCurrent();
//timer1運行在default mode
NSTimer *timer1 = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer1 fired");
}];
[[NSRunLoop currentRunLoop] addTimer:timer1 forMode:NSDefaultRunLoopMode];
//timer2運行在track Mode
NSTimer *timer2 = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer2 fired");
}];
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:UITrackingRunLoopMode];
//指定當前運行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);
}
代碼運行起來后率拒,就可以看到,在觸摸之前只有timer1打印禁荒,在手指觸摸時之后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
通過這樣我們就模擬了系統(tǒng)的mode切換過程猬膨,也順便知道了,為什么NSTimer需要設置在NSRunLoopCommonModes模式下運行呛伴。