上篇文章有说到定时器(NSTimer、CADisplayLink),现在补充上篇遗留的GCD的相关东西
-
GCD的简单使用
这里非常不建议纯手动书写,误差太大,根据代码提示如下所示:
选择上图中蓝色选项,结果如下
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, <#dispatchQueue#>);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
<#code to be executed when timer fires#>
});
dispatch_resume(timer);
最终补充对应的参数等,稍微解释下上面的几个参数
/*
这里需要一个队列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, <#dispatchQueue#>);
/*
第一个参数:定时器对象
第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时,start * NSEC_PER_SEC表示从现在开始经过start秒执行
第三个参数:间隔时间 GCD里面的时间最小单位为 interval * NSEC_PER_SEC表示间隔为interval秒
第四个参数:精准度(表示允许的误差,0表示绝对精准)
*/
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定时器实现
<#code to be executed when timer fires#>
});
接下来看看相对比较完整的写法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
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_source_set_event_handler(timer, ^{
NSLog(@"----");
});
dispatch_resume(timer);
上面基本上算完成,这里必须使用strong类型接收下,不然定时器没法运行
- 一般的GCD写法
@interface AnotherViewController ()
@property (nonatomic, strong) dispatch_source_t gcd_timer;
@end
- (void)viewDidLoad {
[super viewDidLoad];
NSTimeInterval start = 0.0;//开始时间
NSTimeInterval interval = 1.0;//时间间隔
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
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_source_set_event_handler(timer, ^{
NSLog(@"------");
});
self.gcd_timer = timer;
dispatch_resume(self.gcd_timer);
}
-(void)dealloc{
NSLog(@"%s", __func__);
dispatch_source_cancel(self.gcd_timer);
}
运行结果如下:
GCD的定时器写法相对比较固定,这里建议封装,封装之前先理一理思路(GCD定时器的封装必然依赖原有的写法,所以原有的一些关键性的参数必须保留):
1、开始时间
2、时间间隔
3、是否需要重复执行
4、是否支持多线程
5、具体执行任务的函数或者block,其实这里两种都是可以的
6、取消定时任务
上述就是简单的思路,接下来先进行简单的封装,如下所示:
- 初步封装
@interface JCGCDTimer : NSObject
+ (void)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
+(void)canelTimer;
@end
@implementation JCGCDTimer
+ (void)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
对参数做一些限制
1.如果task不存在,那就没有执行的必要(!task)
2.开始时间必须大于当前时间
3.当需要重复执行时,重复间隔时间必须 >0
以上条件必须满足,定时器才算是比较合理,否则没必要执行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return; (上面的代码有人可能会写成这样,都一样,这是if的语法,里面只有一行时候可以省略{},其他的没区别)
/**
队列
async:YES 全局队列 dispatch_get_global_queue(0, 0) 可以简单理解为其他线程(非主线程)
async:NO 主队列 dispatch_get_main_queue() 可以理解为主线程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
创建定时器 dispatch_source_t timer
*/
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_source_set_event_handler(timer, ^{
//定时任务
task();
//如果不需要重复,执行一次即可
if (!repeats) {
dispatch_source_cancel(timer);
}
});
//启动定时器
dispatch_resume(timer);
}
+(void)canelTimer{
}
@end
当我们想取消定时器的时候会思考,多个定时器如何取消呢(很多时候可能会创建不止一个定时器),得需要一个标准,或者说根据一个标记取到对应的定时器,这里我们根据key - value的形式进行完善上述定时器,具体如下:
- 进一步封装(这里关于load、initialize的选择抽时间再讲,必要的话专门开一篇细说)
@interface JCGCDTimer : NSObject
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
+(void)canelTimer:(NSString*) timerName;
@end
@implementation JCGCDTimer
static NSMutableDictionary *timers_;
/**
load 与 initialize区别,这里选用initialize
*/
+(void)initialize{
//GCD一次性函数
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
对参数做一些限制
1.如果task不存在,那就没有执行的必要(!task)
2.开始时间必须大于当前时间
3.当需要重复执行时,重复间隔时间必须 >0
以上条件必须满足,定时器才算是比较合理,否则没必要执行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return nil;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return nil; (上面的代码有人可能会写成这样,都一样,这是if的语法,里面只有一行时候可以省略{},其他的没区别)
/**
队列
async:YES 全局队列 dispatch_get_global_queue(0, 0) 可以简单理解为其他线程(非主线程)
async:NO 主队列 dispatch_get_main_queue() 可以理解为主线程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
创建定时器 dispatch_source_t timer
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 定时器的唯一标识
NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[timerName] = timer;
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定时任务
task();
//如果不需要重复,执行一次即可
if (!repeats) {
[self canelTimer:timerName];
}
});
//启动定时器
dispatch_resume(timer);
return timerName;
}
+(void)canelTimer:(NSString*) timerName{
if (timerName.length == 0) {
return;
}
dispatch_source_t timer = timers_[timerName];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:timerName];
}
}
@end
上面的代码涉及到对字典的存取,所以还需要考虑到线程安全(只需要在存值取值的时候进行处理),因此,需要进一步完善代码,如下所示:
- 最终封装
@interface JCGCDTimer : NSObject
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
+(void)canelTimer:(NSString*) timerName;
@end
@implementation JCGCDTimer
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
/**
load 与 initialize区别,这里选用initialize
*/
+(void)initialize{
//GCD一次性函数
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
对参数做一些限制
1.如果task不存在,那就没有执行的必要(!task)
2.开始时间必须大于当前时间
3.当需要重复执行时,重复间隔时间必须 >0
以上条件必须满足,定时器才算是比较合理,否则没必要执行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return nil;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return nil; (上面的代码有人可能会写成这样,都一样,这是if的语法,里面只有一行时候可以省略{},其他的没区别)
/**
队列
async:YES 全局队列 dispatch_get_global_queue(0, 0) 可以简单理解为其他线程(非主线程)
async:NO 主队列 dispatch_get_main_queue() 可以理解为主线程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
创建定时器 dispatch_source_t timer
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器的唯一标识
NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[timerName] = timer;
dispatch_semaphore_signal(semaphore_);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定时任务
task();
//如果不需要重复,执行一次即可
if (!repeats) {
[self canelTimer:timerName];
}
});
//启动定时器
dispatch_resume(timer);
return timerName;
}
+(void)canelTimer:(NSString*) timerName{
if (timerName.length == 0) {
return;
}
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[timerName];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:timerName];
}
dispatch_semaphore_signal(semaphore_);
}
@end
上面是以block的方式进行封装,当然了,也可以使用target的方式,相对比较简单,具体如下:
#import
@interface JCGCDTimer : NSObject
/**
Block方式的定时器
@param task 任务(这里使用block)
@param start 开始时间
@param interval 间隔
@param repeats 时候否重复调用
@param async 是否子线程
@return 定时器标识(最终取消定时器是需要根据此标识取消的)
*/
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
/**
Target方式的定时器
@param target 目标对象(这里使用方法)
@param selector 调用方法
@param start 开始时间
@param interval 间隔
@param repeats 是否重复调用
@param async 是否子线程
@return 定时其标识(最终取消定时器是需要根据此标识取消的)
*/
+ (NSString*)timerTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
/**
取消定时器
@param timerName 定时器标识
*/
+(void)canelTimer:(NSString*) timerName;
@end
#import "JCGCDTimer.h"
@implementation JCGCDTimer
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
/**
load 与 initialize区别,这里选用initialize
*/
+(void)initialize{
//GCD一次性函数
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
对参数做一些限制
1.如果task不存在,那就没有执行的必要(!task)
2.开始时间必须大于当前时间
3.当需要重复执行时,重复间隔时间必须 >0
以上条件必须满足,定时器才算是比较合理,否则没必要执行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return nil;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return nil; (上面的代码有人可能会写成这样,都一样,这是if的语法,里面只有一行时候可以省略{},其他的没区别)
/**
队列
asyc:YES 全局队列 dispatch_get_global_queue(0, 0) 可以简单理解为其他线程(非主线程)
asyc:NO 主队列 dispatch_get_main_queue() 可以理解为主线程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
创建定时器 dispatch_source_t timer
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器的唯一标识
NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[timerName] = timer;
dispatch_semaphore_signal(semaphore_);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定时任务
task();
//如果不需要重复,执行一次即可
if (!repeats) {
[self canelTimer:timerName];
}
});
//启动定时器
dispatch_resume(timer);
return timerName;
}
+ (NSString*)timerTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async{
if (!target || !selector) return nil;
return [self timerTask:^{
if ([target respondsToSelector:selector]) {
//(这是消除警告的处理)
#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];
}
+(void)canelTimer:(NSString*) timerName{
if (timerName.length == 0) {
return;
}
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[timerName];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:timerName];
}
dispatch_semaphore_signal(semaphore_);
}
@end
最终Controller中的定时器使用
-
block 样式
- (void)viewDidLoad {
[super viewDidLoad];
//这里async传值为YES,即在子线程
NSString* jcdTimer = [JCGCDTimer timerTask:^{
NSLog(@"%@", [NSThread currentThread]);
} start:1 interval:2 repeats:YES async:YES];
self.jcTimer = jcdTimer;
}
-(void)dealloc{
NSLog(@"%s", __func__);
[JCGCDTimer canelTimer:self.jcTimer];
}
运行结果如下:
-
target样式
- (void)viewDidLoad {
[super viewDidLoad];
//这里async传值为NO,即在主线程
NSString* jcdTimer = [JCGCDTimer timerTask:self selector:@selector(jcTimerAction) start:1 interval:2 repeats:YES async:NO];
self.jcTimer = jcdTimer;
}
-(void)jcTimerAction{
NSLog(@"%@", [NSThread currentThread]);
}
-(void)dealloc{
NSLog(@"%s", __func__);
[JCGCDTimer canelTimer:self.jcTimer];
}
运行如下所示:
- 这里我们发现Controller出栈的时候没有自动调用dealloc,定时器一直没有停止,这里存在循环引用,target/self(这个很容易被忽视),参考iOS 定时器(NSTimer/CADisplayLink)循环引用,这篇文章中JCKProxy(集成自NSProxy),最终修改如下:
NSString* jcdTimer = [JCGCDTimer timerTask:[JCKProxy proxyWithTarget:self] selector:@selector(jcTimerAction) start:1 interval:2 repeats:YES async:NO];
self.jcTimer = jcdTimer;
最终运行如下:
GCD小结:
1、GCD定时器不依赖NSRunLoop,他是一个独立的体系
2、精度高,最小到1纳秒
3、没有invalidate方法
4、不需要手动管理内存(这里封装的target方式一定要注意循环引用)
结束语
到这里,GCD定时器相关的内容基本上告一段落,提供了2套GCD的封装,这里建议使用block方式,如果其他问题,欢迎私戳,一起进步,共同迈向大神的行列,年轻人,加油 。。。