当使用NSTimer的scheduledTimerWithTimeInterval方法时。事实上此时Timer会被加入到
当前线程的RunLoop中,且模式是默认的NSDefaultRunLoopMode。而如果当前线程就是主线
程,也就是UI线程时,某些UI事件,比如UIScrollView的拖动操作,会将RunLoop切换成
NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式
中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加
到RunLoop中的Timer就不会执行。
所以为了设置一个不被UI干扰的Timer,我们需要手动创建一个Timer,然后使用
NSRunLoop的addTimer:forMode:方法来把Timer按照指定模式加入到RunLoop中。
这里使用的模式是:NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode
和NSEventTrackingRunLoopMode的结合。(参考Apple文档)
参考代码:
-(void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"主线程 %@",[NSThread currentThread]);
//创建Timer
NSTimer *timer= [NSTimer timerWithTimeInterval:2.0 target:selfselector:
@selector(timer_callback) userInfo:nil repeats:YES];
//使用NSRunLoopCommonModes模式,把timer加入到当前RunLoop中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:
NSRunLoopCommonModes];
}
//timer的回调方法
-(void)timer_callback
{
NSLog(@"Timer%@",[NSThread currentThread]);
}
输出:
主线程
Timer
Timer
Timer
返回目录
上面讲的NSRunLoopCommonModes和Timer中有一个问题,这个Timer本质上是在当前线
程的RunLoop中循环执行的,因此Timer的回调方法不是在另一个线程的。那么怎样在真正的
多线程环境下运行一个Timer呢?
可以先试试NSThread。同上,我们还是会把Timer加到RunLoop中,只不过这个是在另一
个线程中,因此我们需要手动执行RunLoop(通过NSRunLoop的run方法),同时注意在新的
线程执行中加入@autoreleasepool。
完整代码如下:
-(void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"主线程 %@",[NSThread currentThread]);
//创建并执行新的线程
NSThread *thread= [[NSThread alloc] initWithTarget:self selector:
@selector(newThread)object:nil];
[thread start];
}
-(void)newThread
{
@autoreleasepool
{
//在当前RunLoop中添加timer,模式是默认的NSDefaultRunLoopMode
[NSTimer scheduledTimerWithTimeInterval:2.0 target:selfselector:
@selector(timer_callback) userInfo:nil repeats:YES];
//开始执行新线程的RunLoop
[[NSRunLoop currentRunLoop] run];
}
}
//timer的回调方法
-(void)timer_callback
{
NSLog(@"Timer%@",[NSThread currentThread]);
}
输出:
主线程
Timer
Timer
Timer
返回目录
GCD中的Timer应该是最灵活的,而且是多线程的。GCD中的Timer是靠Dispatch Source
来实现的。
因此先需要声明一个dispatch_source_t本地变量:
@interface ViewController ()
{
dispatch_source_t _timer;
}
接着通过dispatch_source_create函数来创建一个专门的DispatchSource,接着通过dispatch_source_set_timer函数来设置Timer的参数,注意这里的时间参数有些蛋疼。
开始时间的类型是dispatch_time_t,最好用dispatch_time或者dispatch_walltime函数
来创建dispatch_time_t对象。如果需要Timer立即执行,可以传入dispatch_time(DISPATCH_TIME_NOW,0)。
internal和leeway参数分别表示Timer的间隔时间和精度。类型都是uint64_t。间隔时间的单
位竟然是纳秒。可以借助预定义的NSEC_PER_SEC宏,比如如果间隔时间是两秒的话,那
interval参数就是:2* NSEC_PER_SEC。
leeway就是精度参数,代表系统可以延时的时间间隔,最高精度当然就传0。
然后通过dispatch_source_set_event_handler函数来设置DispatchSource的事件回调,
这里当然是使用Block了。
最后所有dispatch_source_t创建后默认都是暂停状态的,所以必须通过dispatch_resume函数
来开始事件监听。这里就代表着开始Timer。
完整代码:
NSLog(@"主线程 %@",[NSThread currentThread]);
//间隔还是2秒
uint64_t interval= 2 * NSEC_PER_SEC;
//创建一个专门执行timer回调的GCD队列
dispatch_queue_t queue= dispatch_queue_create("myqueue", 0);
//创建Timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
//使用dispatch_source_set_timer函数设置timer参数
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0),
interval, 0);
//设置回调
dispatch_source_set_event_handler(_timer,^()
{
NSLog(@"Timer%@",[NSThread currentThread]);
});
//dispatch_source默认是Suspended状态,通过dispatch_resume函数开始它
dispatch_resume(_timer);
输出:
主线程
Timer
Timer
Timer