美文网首页想法简友广场
一、RunLoop初体验~思考

一、RunLoop初体验~思考

作者: LonelyBanana | 来源:发表于2022-07-04 16:39 被阅读0次
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%ld",(long)count++);
        }];
    
    [self.view addSubview:self.tv_list];
    

    写了个UITableView然后我添加个NSTimer,接下来回发生什么事情呢?
    当滑动UITableView时,NSTimer计时器就会停止。
    相信绝大多数开发者都遇到过这个问题,当然百度一下会有很多答案。

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

    添加这代码就完美的解决了这个问题,但是为什么会这样呢?
    猜测肯定跟NSRunLoop有关。那NSRunLoop是什么?

    1、NSRunLoop是什么?

    截屏2022-07-04 14.21.09.png

    NSRunLoop对象处理源的输入,例如来自窗口系统和NSPort对象的鼠标和键盘事件。NSRunLoop对象也处理NSTimer事件。

    你的应用程序既不创建也不显式管理NSRunLoop对象。系统根据需要为每个NSThread对象创建一个NSRunLoop对象,包括应用程序的主线程。如果您需要访问当前线程的运行循环,请使用类方法currentRunLoop

    注意,从NSRunLoop的角度来看,NSTimer对象不是“输入”——它们是一种特殊的类型,当它们触发运行循环时,它们不会导致运行循环返回。

    似乎也看不明白说的什么意思。
    通俗易懂的话这玩意是个死循环,在有事件响应的时候,RunLoop去处可以响应处理一些事件,没有事件响应的时候RunLoop会进入休眠,并且他和线程是一一对应的。一个线程里面有的话只可以拥有一个RunLoop。

    2、NSRunLoopMode是什么?

    035a7f84ea33411db674f75f9538fb92_tplv-k3u1fbpfcp-watermark.png

    Modes that a run loop operates in. 各位自行翻译吧。

    1264209451244f0aa91229e7c03f445f_tplv-k3u1fbpfcp-zoom-in-crop-mark_3024_0_0_0.jpg

    目前NSRunLoopMode有这5种,但是只有NSDefaultRunLoopMode UITrackingRunLoopModel NSRunLoopCommonModes这三种是用于iOS上面的,剩下两种是用于MacOS上的。

    NSDefaultRunLoopMode

    c8c2dc621768408783c77c26ec85cb75_tplv-k3u1fbpfcp-zoom-in-crop-mark_3024_0_0_0.jpg

    设置为处理连接对象以外的输入源的模式。

    UITrackingRunLoopModel

    467fea02a857496caefc190c009d3f83_tplv-k3u1fbpfcp-watermark.png

    在控件中进行跟踪时设置的模式。

    NSRunLoopCommonModes

    e85886ffe56e43ccbe26d4c4736ebc30_tplv-k3u1fbpfcp-watermark.png

    包含一个或多个其他运行循环模式的伪模式。
    其实这个伪模式中包含了NSDefaultRunLoopMode|UITrackingRunLoopModel这两种模式

    3、测试

    __block NSInteger count = 0;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%ld",(long)count++);
        //添加这行代码
        NSLog(@"Timer的currentModel=%@",[[NSRunLoop currentRunLoop] currentMode]);
        }];
    
    **RunLoopTest[58445:4388936] Timer的currentModel=kCFRunLoopDefaultMode**
    

    NSTimer在运行过程中打印出来的Mode为kCFRunLoopDefaultMode这个mode其实这个mode就等于NSDefaultRunLoopMode 因为NSRunLoop是基于CKRunLoop开发的。所以NSTimer在计时时RunLoop的Model为NSDefaultRunLoopMode

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
        NSLog(@"TableView滑动---%@", [[NSRunLoop currentRunLoop] currentMode]);
    }
    

    接下来在scrollViewDidScroll这个代理方法里面添加上滑动监听

    **RunLoopTest[58641:4397470] TableView滑动---UITrackingRunLoopMode**
    

    所以基本上可以分析出来为什么在UITableView滑动的时候NSTimer计时不起作用,因为在UITableView滑动的时候当前主线程的RunLoop的Mode切换到了UITrackingRunLoopMode这个模式但是NSTimer只在NSDefaultRunLoopMode做响应,当UITableView停止的时候RunLoop的Mode又会切回到NSDefaultRunLoopMode,所以如果把NSTimer在当前RunLoop中的模式添加为NSRunLoopCommonModes那其实可以理解为NSTimer可以在NSDefaultRunLoopMode | UITrackingRunLoopModel这两种模式中都起作用。

    当添加了[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];这行代码后:

    **RunLoopTest[80618:4452646] 0**
    **RunLoopTest[80618:4452646] Timer的currentModel=kCFRunLoopDefaultMode**
    **RunLoopTest[80618:4452646] 1**
    **RunLoopTest[80618:4452646] Timer的currentModel=kCFRunLoopDefaultMode**
    **RunLoopTest[80618:4452646] TableView滑动---UITrackingRunLoopMode**
    **RunLoopTest[80618:4452646] 2**
    **RunLoopTest[80618:4452646] Timer的currentModel=UITrackingRunLoopMode**
    

    可以发现当UITableView滑动的时候确实Timer的Mode变为了UITrackingRunLoopMode

    接下来我如果把Timer的Mode直接添加成UITrackingRunLoopMode是否可行呢?

    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    **RunLoopTest[80891:4465038] 0**
    **RunLoopTest[80891:4465038] Timer的currentModel=kCFRunLoopDefaultMode**
    **RunLoopTest[80891:4465038] TableView滑动---UITrackingRunLoopMode**
    **RunLoopTest[80891:4465038] TableView滑动---UITrackingRunLoopMode**
    **RunLoopTest[80891:4465038] 1**
    **RunLoopTest[80891:4465038] Timer的currentModel=UITrackingRunLoopMode**
    

    证明也是可以一变滑动一遍计时,效果是一样的。

    更多的思考

    上面提到了一个线程只会对应一个RunLoop,那如果在开辟一个线程去做计时但是不添加Timer的Model是否可以实现一样的效果呢?

    - (void)viewDidLoad {
        [super viewDidLoad];
        //开启线程
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimer) object:nil];
        [thread start];
        [self.view addSubview:self.tv_list];
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    }
    -(void)startTimer{
        __block NSInteger count = 0;
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%ld",(long)count++);
            NSLog(@"Timer的currentModel=%@",[[NSRunLoop currentRunLoop] currentMode]);
        }];
    }
    
    

    创建了这段代码,但是发现Timer根本没有执行。这是因为什么呢?
    原因是因为主线程的NSRunLoop是系统自己创建的所以不需要我们去创建它,但是我们创建的子线程是没有NSRunLoop的所以需要我们手动的去创建它才行。
    添加这行代码[[NSRunLoop currentRunLoop] run];

    - (void)viewDidLoad {
        [super viewDidLoad];
        //开启线程
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimer) object:nil];
        [thread start];
        [self.view addSubview:self.tv_list];
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    }
    -(void)startTimer{
        __block NSInteger count = 0;
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%ld",(long)count++);
            NSLog(@"Timer的currentModel=%@",[[NSRunLoop currentRunLoop] currentMode]);
        }];
        //创建RunLoop
        [[NSRunLoop currentRunLoop] run];
    }
    

    这样NSTimer正常的跑了起来。

    结论:
    所以经过上面的时间得出NSTimer在UITableView滑动时无法工作是因为NSTimer工作是依靠RunLoop,而UITableView的滑动响应也是依靠RunLoop,但是NSTimer默认是在NSDefaultRunLoopMode模式下去触发响应的,而UITableView的滑动时主线程的RunLoop会切换到UITrackingRunLoopMode模式,所以NSTimer无法在UITableView滑动的时候工作,解决办法比较简单地就是给NSTimer添加NSRunLoopCommonModes这种伪模式或者直接添加UITrackingRunLoopMode模式。在本文中提到了创建一个子线程并且给子线程添加RunLoop去解决NSTimer滑动计时的问题,作者这里不推荐这种方式来实现,因为在实际开发中很容易出现循环引用的问题,次方法只是为了测试下子线程的RunLoop,没必要画蛇添足去解决这种简单的问题。

    相关文章

      网友评论

        本文标题:一、RunLoop初体验~思考

        本文链接:https://www.haomeiwen.com/subject/fxgdbrtx.html