我们上次在内存管理一:NSTimer讲了NSTimer
和CADisplayLink
的循环引用的问题.实际上这两个计时器并不是一定准时的,因为他们都依赖于runloop
,如果runloop
中有耗时的操作,那么定时器事件的调用就会受到影响.
所以如果想让定时器准确的执行任务,最好使用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());
//设置事件
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的定时器,并且成功的运行了.即使往ViewController
中添加一个textView
,滚动textView
仍然不会影响即使工作,因为GCD的计时器并不依赖与runloop
,他是直接和系统内核挂钩的.
下面我们就来封装一个GCD的定时器,方便以后使用:
@interface GoodTimer : NSObject
//添加任务,创建定时器后会返回定时器的唯一标识
+ (NSString *)executeTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
//添加任务,采用target sel 的方式
+ (NSString *)executeTaskWithTarget:(id)target
selector:(SEL)sel
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
//根据唯一标识,停止任务
+ (void)cancelTask:(NSString *)timerIdentifier;
@end
------------------------------------------------------------------------------
#import "GoodTimer.h"
static NSMutableDictionary *timerDic_;
/*因为创建任务和取消任务都会访问timerDic_
,如果是多线程的话,很可能出现问题,
所以要做加锁解锁操作*/
static dispatch_semaphore_t semaphore_;
@implementation GoodTimer
//initialize会在这个类第一次接受消息的时候调用
+ (void)initialize{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timerDic_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString *)executeTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async{
if (!task || start < 0 || (repeats && interval <= 0)) {
return nil;
}
//根据 async 决定是主线程还是子线程
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);
#pragma mark 涉及到字典读取和写入的操作需要加锁,解锁
//加锁
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
//获取identifier
NSString *identifier = [NSString stringWithFormat:@"%zd",[timerDic_ count]];
//把定时器加入到timerDic_
timerDic_[identifier] = timer;
//解锁
dispatch_semaphore_signal(semaphore_);
//设置回调
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) {
//不重复,就取消定时器
[self cancelTask:identifier];
}
});
//启动定时器
dispatch_resume(timer);
return identifier;
}
+ (NSString *)executeTaskWithTarget:(id)target selector:(SEL)sel start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async{
return [self executeTask:^{
if ([target respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Warc-performSelector-leaks"
[target performSelector:sel];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
//取消任务
+ (void)cancelTask:(NSString *)timerIdentifier{
if (timerIdentifier.length == 0) {
return;
}
//加锁
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timerDic_[timerIdentifier];
if (timer) {
//取消任务
dispatch_source_cancel(timerDic_[timerIdentifier]);
//把任务从timerDic_中移除
[timerDic_ removeObjectForKey:timerIdentifier];
}
//解锁
dispatch_semaphore_signal(semaphore_);
}
@end
网友评论