.iOS中三种定时器使用方式和区别
1.NSTimer
NSTimer启动方式有两种,第一种创建之后启动,第二种scheduled自启动,区别是第一种创建之后需要手动添加到NSRunLoop中才能生效,第二种是默认会添加到当前NSRunLoop中。
注:每个线程都有自己的NSRunLoop,只有主线程是默认开启的,子线程需要手动开启。NSTimer添加到开启的NSRunLoop中才会执行(见RunLoop和NSTimer)。
当前NSRunLoop很忙的时候会出现延迟不精准的现象,或者当前RunLoop模式不是timer源,则timer会停止运行。
a.添加到主线程循环的timer
NSTimer * timer=[NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
[timer setFireDate:[NSDate distantFuture]];
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
b.默认添加到主线程循环的timer
NSTimer * timer=[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
开个子线程证明线程NSRunLoop的问题
如果把timer添加到NSRunLoop currentRunLoop中,而不启动该子线程runloop,那么定时器不会执行,必须手动启动子线程的runloop -->[[NSRunLoopcurrentRunLoop]runUntilDate:[NSDatedistantFuture]];
或者添加到主线程runloop中执行timer。
-->[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self->timer=[NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
[self->timer setFireDate:[NSDate distantPast]];
[[NSRunLoop currentRunLoop]addTimer:self->timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop]runUntilDate:[NSDate distantFuture]];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self->timer=[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
[[NSRunLoop currentRunLoop]runUntilDate:[NSDate distantFuture]];
});
关于停止NSTimer的问题,停止的代码如下,目的是把timer从当前NSRunLoop中移除出去,并把timer指针置空,但是主线程NSTimer停止在主线程执行,子线程NSTimer停止要在子线程执行。
子线程NSTimer如果在主线程停止,那么"go on"不会执行。
[timer invalidate];
timer=nil;
NSOperationQueue * queue=[[NSOperationQueue alloc]init];
NSBlockOperation * operation=[NSBlockOperation blockOperationWithBlock:^{
self->timer=[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
[[NSRunLoop currentRunLoop]runUntilDate:[NSDate distantFuture]];
NSLog(@"go on");
}];
[queue addOperation:operation];
iOS10之前的方法
timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(test) userInfo:nil repeats:YES]
不手动调用[time invalidate];timer=nil;viewcontroller和timer造成循环引用,不会回收。
IOS10之后的方法
self->timer=[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
不手动调用[time invalidate];timer=nil;只要viewcontroller回收,timer也被回收。
2.CADisplayLink
基本使用方式,NSRunLoop的道理同NSTimer。
CADisplayLink最小刷新频率等同于屏幕1/60秒,只有CPU过忙,屏幕刷新频率小于1/60时才会出现不精确。
CADisplayLink * dislink=[CADisplayLink displayLinkWithTarget:self selector:@selector(dislink:)];
dislink.preferredFramesPerSecond=1;
[dislink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
3.dispatch_source_t
不需要手动添加timer到runloop,只需要指定主线程队列还是子线程队列,注意事项就是timer要设置成为全局成员变量,被该对象引用,不然执行一次自动回收。
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 1.0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
});
dispatch_resume(timer);
区别:
简单来讲就是精准度CADisplayLink>dispatch_source>NSTimer
实现子线程timer使用dispatch_source更好
CADisplayLink使用场合相对专一,适合做UI的不停重绘,给非UI对象添加动画效果,比如当播放视频时关掉视频的声音我可以通过CADisplayLink来实现一个EasyOut的渐出效果:先快速的降低音量,在慢慢的渐变到静音。
dispatch_source可以使用子线程,解决跑在主线程上卡UI问题,但是如果使用主线程刷新UI,记得回调到主线程。dispatch_async(dispatch_main(), ^})
注:对于子线程定时器当然可以使用NSTimer通过启动子线程NSRunLoop和添加timer到该RunLoop上的方式,但是使用dispatch_source更方便。