iOS NSTimer在scroll view滚动时失效

参考文章:
http://www.cnblogs.com/ibelieveyou/p/6780098.html
http://blog.csdn.net/sky_warmer/article/details/46335049
https://www.mgenware.com/blog/?p=459#_h1

问题描述

我们创建一个这样的场景

  • 有一个tableView,cell上会显示一个随着NSTimer周期变化的数字
  • 我们启动timer后,在tableView没有滚动的情况下cell中的数字会随着timer变化
  • 当我们开始滑动tableView时,cell中的数字停止了变化,我们创建的timer暂停了
iOS NSTimer在scroll view滚动时失效_第1张图片

原因分析

当我们使用scheduledTimerWithTimeInterval方法创建了一个timer时,系统创建了一个timer并添加到了当前runloop中,且模式默认为NSDefaultRunLoopMode模式.
在我们的例子当中timer就被添加进了主线程中,我们知道主线程同时也是UI线程,在UI线程中为了保证UI事件不被其他事件所干扰,在触发某些事件时(例如scrollView的滚动),runloop会将模式切换到NSEventTrackingRunLoopMode,当runloop在NSEventTrackingRunLoopMode模式下时,NSDefaultRunLoopMode模式下的方法是不会被执行的
这就是我们在拖动tableView的时候数字停止变化的原因

解决方法

我们处理的方法有两个

  1. 在将timer添加到主线程中时将runloop的模式改变成不受影响的模式
  2. 将timer添加到一个子线程中

方案一:修改runloop模式

在runloop中公开的有两种模式


NSDefaultRunLoopMode和NSRunLoopCommonModes
我们查询文档

iOS NSTimer在scroll view滚动时失效_第2张图片
NSDefaultRunLoopMode
iOS NSTimer在scroll view滚动时失效_第3张图片
NSRunLoopCommonModes
iOS NSTimer在scroll view滚动时失效_第4张图片
CFRunLoopAddCommonMode

NSDefaultRunLoopMode:这个模式处理出了NSCOnnection对象之外的所有输入源的模式,是最常用的runloop模式
NSRunLoopCommonModes:对象在被添加到runloop中的时候使用了NSRunLoopCommonModes模式时,这些对象的模式将被放入到一个common的模式集合中,这个集合中的所有模式都能被共同的被监听
解释一下这句话,当一个source,timer和observers被以某种模式添加到一个或多个runloop中时,只有当runloop运行在这些source,timer和observers注册的模式下的时候,他们才能被响应,但是当我们使用NSRunLoopCommonModes模式时,这些source,timer和obsersvers注册的模式将被放入一个叫做common的模式集,这个模式集中的各种模式的响应是互通的,这样就可以让这些source,timer和observers的响应在runloop运行的模式与注册时的模式不同时也能响应

我们将添加timer时我们手动将模式设置为NSRunLoopCommonModes

- (void)setupTimer {
    __weak typeof(self) weakself = self;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.2 repeats:YES block:^(NSTimer * _Nonnull timer) {
        self.time += 1;
        [weakself.tableView reloadData];
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    [timer fire];
}
iOS NSTimer在scroll view滚动时失效_第5张图片

方案二:创建一个子线程

/*
 *  设置子线程
 */
- (void)setupTimer {
    //创建一个子线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //创建timer,并回调refreshData方法
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(refreshData) userInfo:nil repeats:YES];
        //将timer添加到新线程的runloop中
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        //启动runloop
        [[NSRunLoop currentRunLoop] run];
    });
}

- (void)refreshData {
    __weak typeof(self) weakself = self;
    
    /*
     *  GCD方式回到主线程
     */
    //回到主线程
//    dispatch_async(dispatch_get_main_queue(), ^{
//        //刷新显示数据
//        weakself.time += 1;
//        [weakself.tableView reloadData];
//    });
    
    /*
     *  队列方式回到主线程
     */
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        //刷新显示数据
        weakself.time += 1;
        [weakself.tableView reloadData];
    }];
}

同样能达到效果

demo:https://github.com/TigerCui/iOSDemo/tree/master/NSTimerInScrollViewDemo

你可能感兴趣的:(iOS NSTimer在scroll view滚动时失效)