模擬主線程RunLoop的mode的切換

對(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)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容