OC-使用GCD封装定时器
image-20210525124406608
NSTimer
和CADisplayLink
实际上这两个计时器并不是一定准时的,因为他们都依赖于runloop
,如果runloop
中有耗时的操作,那么定时器事件的调用就会受到影响.
NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时。
如果RunLoop专门做NSTimer的事情的话,那么NSTimer是准时的 ,如果RunLoop除了在做NSTimer的事情外还做其他事情,那么会导致NSTimer不准时。
就比如说NSTimer是1s执行一次,可能它跑完第一圈发现才用了0.5s,这时候发现还没到1s,所以NSTimer不会执行,但是跑第二圈的时候任务就多了可能就需要0.8s,跑完两圈一共1.3s,这时候发现超过1s了,就会执行NSTimer,这时候NSTimer就不准了,晚了0.3s。
所以如果想让定时器准确的执行任务,最好使用GCD的定时器.
@interface ViewController ()
@property (nonatomic,strong)dispatch_source_t timer ;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//创建一个定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//设置事件
/**
定时器设置
@param 定时器
@param 什么时候开始
@param 定时器延迟多久
@param 每隔几秒执行
@param 允许多少误差
*/
//GCD要求传入纳秒,所以要用秒乘以NSEC_PER_SEC
dispatch_source_set_timer(
self.timer,
dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC),//开始事件,从 3 秒后开始
1.0 * NSEC_PER_SEC,//没间隔 1 秒
0
);
//设置定时器回调,采用block的方式
// dispatch_source_set_event_handler(self.timer, ^{
// NSLog(@"定时器事件");
// });
//设置定时器回调,采用 函数方式 _f 是function
dispatch_source_set_event_handler_f(self.timer, timerAction);
//启动定时器
dispatch_resume(self.timer);
}
void timerAction(void *para){
NSLog(@"定时器事件");
}
@end
GCD的定时器是和系统内核挂钩的,所以就算界面上添加一个scrollView,滚动的时候就算RunLoop模式切换了,GCD定时器还会照常工作,因为GCD和RunLoop一点关系都没有.
GCD定时器的封装
#import "MJTimer.h"
@implementation MJTimer
//只初始化一次
static NSMutableDictionary *timers_; //保存定时器的字典
dispatch_semaphore_t semaphore_; //信号量
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
/**
封装GCD定时器
@param task 任务block
@param start 开始
@param interval 间隔
@param repeats 是否重复
@param async 是否异步
@return 返回定时器唯一标识
*/
+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 队列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
//对字典读写,加信号量锁,保证创建任务和取消任务同时只有一个在做
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器的唯一标识
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重复的任务
[self cancelTask:name];
}
});
// 启动定时器
dispatch_resume(timer);
return name;
}
/**
封装GCD定时器
@param target 消息发送者
@param selector 消息
@param interval 间隔
@param repeats 是否重复
@param async 是否异步
@return 返回定时器唯一标识
*/
+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!target || !selector) return nil;
return [self execTask:^{
if ([target respondsToSelector:selector]) {
//强制消除Xcode警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
/**
取消任务
@param name 根据唯一标识取消任务
*/
+ (void)cancelTask:(NSString *)name
{
if (name.length == 0) return;
//对字典读写,加信号量锁,保证创建任务和取消任务同时只有一个在做
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
//从字典中移除定时器
dispatch_source_t timer = timers_[name];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_);
}
@end
特别备注
本系列文章总结自MJ老师在腾讯课堂iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!