参考文章:
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暂停了
原因分析
当我们使用scheduledTimerWithTimeInterval方法创建了一个timer时,系统创建了一个timer并添加到了当前runloop中,且模式默认为NSDefaultRunLoopMode模式.
在我们的例子当中timer就被添加进了主线程中,我们知道主线程同时也是UI线程,在UI线程中为了保证UI事件不被其他事件所干扰,在触发某些事件时(例如scrollView的滚动),runloop会将模式切换到NSEventTrackingRunLoopMode,当runloop在NSEventTrackingRunLoopMode模式下时,NSDefaultRunLoopMode模式下的方法是不会被执行的
这就是我们在拖动tableView的时候数字停止变化的原因
解决方法
我们处理的方法有两个
- 在将timer添加到主线程中时将runloop的模式改变成不受影响的模式
- 将timer添加到一个子线程中
方案一:修改runloop模式
在runloop中公开的有两种模式
NSDefaultRunLoopMode和NSRunLoopCommonModes
我们查询文档
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];
}
方案二:创建一个子线程
/*
* 设置子线程
*/
- (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