为什么突然对GCD进行一个Timer的介绍和封装呢?主要是因为最近在面试的时候,面试官问了我一个问题:
“如果要你用GCD进行一个Timer的创建和封装,你会怎么做?”
那这篇文章我们就来介绍一下如果使用GCD进行Timer的使用和封装
这里是我封装的GCDTimer的代码,大家可以去看看。开箱即用。
一:GCD定时器介绍
GCD的定时器使用了dispatch source对系统内核对象进行监听和处理。而这就有点类似生产者消费者模式,通过监听对象,然后在生产数据后通知相应的dispatch队列执行。
二:为什么要使用GCD定时器呢?而不使用NSTimer呢?
与系统提供的NSTimer来说,NSTimer有以下的问题:
- NSTimer必须保证在一个活跃的Runloop里面,在主线程这是默认开启的,而在子线程是没有的。所以必须把他加入到一个Runloop里面。
- NSTimer的创建与撤销必须在同一个线程操作。
- 同时因为Runloop的Mode的问题,在ScrollView滚动时,会导致NSTimer的延迟
- 引起循环引用。因为会NSTimer的Target在Repeats的情况下,会对target进行引用。所以会导致释放异常。
三:如何创建GCD定时器:
GCD的定时器主要使用了以下几个函数:
-(void)GCDTimer{
/**创建定时器对象
* timer是一个`dispatch_source_t`类型,需要要全局定义。局部定义初始化生命周期太短,不会执行回调。
* 第一个参数:是定义了sourcer的类型为定时器类型.
* 第二个、三个参数,对于定时器类型的dispatch_source_t无效.
* 最后一个参数是设置队列.
*/
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
/**设置定时器
* 第一个参数是dispatch源,就是刚刚创建的那个Timer
* 第二个参数何时执行。一般来说如果是立即执行则传入DISPATCH_TIME_NOW即可。
* 如果需要延迟执行,则可以通过dispatch_time创建`dispatch_time_t`类型。【dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC) 即为延迟2秒执行】
* 第三个参数是间隔。
* 第四个是误差值。如果不需要误差,则为0即可
*/
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
__weak typeof(self) weakSelf = self;
/**设置定时器的回调函数
* 记得在block里面的循环引用
* 如果在子线程的定时器,刷新UI,需要返回到主线程。
*/
dispatch_source_set_event_handler(self.timer, ^{
[weakSelf doSomeThing];
});
/*开启或者恢复定时器。首次创建完后,必须调用该方法以开启定时器。这样才会接受到回调函数**/
dispatch_resume(self.timer);
/*取消定时器*/
dispatch_source_cancel(self.timer);
/*挂起定时器器(即暂停定时器)**/
//dispatch_suspend(self.timer);
}
四、敲重点!!
1. GCD的定时器没有非repeat的选项.
- 补充一点,OC版本的是没有,可是在Swift版本可以进行设置的。
- 所以如果我们的定时器不想循环使用,则必须手动加入一个标志位去判断。
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(self.timer, ^{
if(!weakSelf.isRepeat){
dispatch_source_cancel(weakSelf.timer);
}
});
2. dispatch_suspend 和 dispatch_resume 进行配对使用。
- 因为在调用了
dispatch_suspend后,GCD会对suspend进行挂起数量的记录。 - 所以在调用了多次
dispatch_suspend,就必须调用相等数量的dispatch_resume。直到挂起数量为0时,定时器才会真正的恢复。(如你在一个操作里面调用了3次dispatch_suspend,那么你就要在恢复的时候,调用3次dispatch_resume才可以。)
3. 无法获取dispatch_suspend的挂起数和状态。
- 所以在调用
dispatch_suspend的时候需要定义一个变量去记录。
4. 取消定时器的注意事项
- 当定时器的挂起数大于0的时候,对dispatch_source_t进行nil设置,这样都会导致Crash。
dispatch_suspend(self.timer);
dispatch_source_cancel(self.timer);
self.timer = nil; //Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
下一篇我将介绍一下我的封装思路。上面如有错误或者说的不好,欢迎大家指出~
如果我的文章能帮到你,麻烦点赞关注,谢谢了~
最后祝大家生活愉快~