NSTimer 依赖于 RunLoop,如果 RunLoop 的任务过于繁重,可能会导致 NSTimer 不准时。
而 GCD 定时器是直接和系统内核挂钩的,并不依赖于 RunLoop,所以通常使用 GCD 的定时器会更加准时。
话不多说,直接上代码:
//
// QLTimer.m
// GCDTimerTest
//
// Created by 小零钱 on 2019/5/10.
// Copyright © 2019 QLing. All rights reserved.
//
#import "QLTimer.h"
@interface QLTimer ()
@end
@implementation QLTimer
static NSMutableDictionary * _timersDic;
dispatch_semaphore_t _semaphore; //操作全局字典的地方需要加锁,防止多条线程竞争资源、同时修改全局字典的内容
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_timersDic = [NSMutableDictionary dictionary];
_semaphore = dispatch_semaphore_create(1);
});
}
/**
GCD定时器的封装
@param target 定时器执行任务的对象
@param selector 定时器执行任务的方法
@param start 开始时间(延迟几秒执行)
@param interval 定时器的任务执行间隔时间
@param repeats 是否重复执行任务
@param async 是否异步执行任务
@return 当前任务的唯一标识
*/
+ (NSString *)executeTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async {
if (!target || !selector) return nil;
return [self executeTask:^{
if ([target respondsToSelector:selector]) {
//强制去除Xcode的警告
#pragma clang diagnostic pushleaks
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
/**
GCD定时器的封装
@param task 定时器要执行的任务
@param start 开始时间(延迟几秒执行)
@param interval 定时器的任务执行间隔时间
@param repeats 是否重复执行任务
@param async 是否异步执行任务
@return 当前任务的唯一标识
*/
+ (NSString *)executeTask:(timerTask)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", _timersDic.count];
//将timer存放到字典中,一个timer对应一个标识
_timersDic[name] = timer;
dispatch_semaphore_signal(_semaphore);
dispatch_source_set_event_handler(timer, ^{
task();
//不重复执行
if (!repeats) [self cancelTask:name];
});
dispatch_resume(timer);
return name;
}
/**
取消某个任务
@param name 要取消的任务的唯一标识
*/
+ (void)cancelTask:(NSString *)name {
if (name.length == 0) return;
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = _timersDic[name];
if (timer) {
dispatch_source_cancel(_timersDic[name]);
[_timersDic removeObjectForKey:name];
}
dispatch_semaphore_signal(_semaphore);
}
@end
基本内容都在上面了,如果还有不明白的地方,请戳这里:下载 demo