在iOS开发过程中,我们常常需要在某个时间后执行某个方法,或者是按照某个周期一直执行某个方法,这时我们就需要用到定时器,而在iOS中,常用的定时器有以下三种NSTimer、CADisplayLink、GCD,下面对这几种方法的创建、执行、销毁及优缺点做个详解:
1. NSTimer
创建方式
//方式一
self.timer = [NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:@selector(timerTest)
userInfo:nil
repeats:YES];
//方式二
self.timer = [NSTimer timerWithTimeInterval:2
target:self
selector:@selector(timerTest)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
- 使用 scheduled 开头创建的定时器,系统会自动的把它加入到 runLoop 的 NSDefaultRunLoopMode 中,缺点是不能更改mode模式
- 使用 timerWithTimeInterval 开头创建的定时器,需要手动的把他添加到runLoop中,同时设置它的mode运行模式。
定时执行(以下就是我们设置定时器需要做的事件)
- (void)timerTest {
NSLog(@"==========NSTimer===============");
}
暂停、继续
暂停
self.timer.fireDate = [NSDate distantFuture];
继续
self.timer.fireDate = [NSDate distantPast];
- 应用场景 比如,在页面消失的时候关闭定时器,然后等页面再次打开的时候,又开启定时器(主要是为了防止它在后台运行,占用CPU)
销毁
[self.timer invalidate];
self.timer = nil;
RunLoop 有五种运行模式,其中第1、5种为苹果对外提供的mode
1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
-
常见问题
在我们平时开发中可能遇到过,当我们使用NSTimer每一段时间执行一些事情时滑动UIScrollView,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复。那是因为Mode的切换造成的,当我们在主线程使用定时器,此时RunLoop的Mode为kCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时我们滑动ScrollView时,RunLoop的Mode会切换到UITrackingRunLoopMode,因此在主线程的定时器就不在管用了,调用的方法也就不再执行了,当我们停止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,所有NSTimer就又管用了。 -
解决方案
- 将NSTimer添加到两种模式下
- 使用占位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的都可以运行,而kCFRunLoopDefaultMode和UITrackingRunLoopMode就被打上commonModel标记。
NSTimer 缺点
计时不精确:不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。
2.CADisplayLink
- 他是苹果专门提供的一个类,主要的优势在于他的执行频率是根据设备屏幕的刷新频率来计算的,也即是时间间隔最准确的定时器。
创建方式
self.displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(timerTest)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
定时执行
- (void)timerTest {
NSLog(@"==========CADisplayLink===============");
}
暂停、继续
添加一个按钮用于控制暂停继续(或其他点击事件)
pasued
属性是控制计时器暂停与恢复,设置为YES的时候会暂停事件的触发。
- (void) displayLinkStart {
self.displayLink.paused = !self.displayLink.paused;
}
销毁
- (void)stopDisplayLink {
[self.displayLink invalidate];
self.displayLink = nil;
}
优缺点
-
优点: 依托于设备屏幕刷新频率触发事件,所以其触发时间上是最准确的。也是最适合做UI不断刷新的事件,过渡相对流畅,无卡顿感。
-
缺点:
- 由于依托于屏幕刷新频率,若果CPU不堪重负而影响了屏幕刷新,那么我们的触发事件也会受到相应影响。
- selector触发的时间间隔只能是duration的整倍数
- selector事件如果大于其触发间隔就会造成掉帧现象。
相关属性
@property(readonly, nonatomic) CFTimeInterval timestamp; //获取上一次selector被执行的时间戳
@property(readonly, nonatomic) CFTimeInterval duration; //获取当前设备的屏幕刷新时间间隔
@property(getter=isPaused, nonatomic) BOOL paused; //控制计时器暂停与恢复的属性
//事件触发间隔。是指两次selector触发之间间隔几次屏幕刷新,默认值为1,也就是说屏幕每刷新一次,执行一次selector,这个也可以间接用来控制动画速度
@property(nonatomic) NSInteger frameInterval
CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond");
@property(nonatomic) NSInteger preferredFramesPerSecond;
3. GCD-dispatch_source_t
创建 添加 执行方法
- (void)addDisPatch_source {
//1.创建
self.disTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//2.设置时间
dispatch_source_set_timer(self.disTimer, dispatch_walltime(NULL,0 * NSEC_PER_SEC), 1 * NSEC_PER_SEC, 0);
//3.执行
dispatch_source_set_event_handler(self.disTimer, ^{
NSLog(@"=========dispatch_source_t=========");
});
}
参数:
dispatch_source_create()相关参数
参数 | 意义 |
---|---|
type | dispatch源可处理的事件 |
handle | 可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID |
mask | 可以理解为描述,提供更详细的描述,让它知道具体要监听什么 |
queue | 自定义源需要的一个队列,用来处理所有的响应句柄(block) |
dispatch_source_set_timer()相关参数
参数 | 意义 |
---|---|
source | dispatch_source_t |
start | 事件首次触发的延迟时间 |
interval | 时间间隔 |
leeway | 误差范围 |
开始 暂停
//开始
dispatch_resume(self.disTimer);
//暂停
dispatch_suspend(self.disTimer);
销毁
dispatch_source_cancel(self.disTimer);
注意:dispatch_source_t 一定要被设置为成员变量,否则将会立即被释放。
优缺点
- 优点:不受当前runloopMode的影响。
- 缺点:虽然不受runloopMode的影响,但是其计时效应仍不是百分之百准确的。
网友评论