OC-使用GCD封裝定時器
image-20210525124406608
NSTimer
和CADisplayLink
實際上這兩個計時器并不是一定準時的,因為他們都依賴于runloop
,如果runloop
中有耗時的操作,那么定時器事件的調(diào)用就會受到影響.
NSTimer依賴于RunLoop温眉,如果RunLoop的任務過于繁重类溢,可能會導致NSTimer不準時露懒。
如果RunLoop專門做NSTimer的事情的話,那么NSTimer是準時的 懈词,如果RunLoop除了在做NSTimer的事情外還做其他事情钦睡,那么會導致NSTimer不準時。
就比如說NSTimer是1s執(zhí)行一次洒琢,可能它跑完第一圈發(fā)現(xiàn)才用了0.5s褐桌,這時候發(fā)現(xiàn)還沒到1s,所以NSTimer不會執(zhí)行呛踊,但是跑第二圈的時候任務就多了可能就需要0.8s啦撮,跑完兩圈一共1.3s,這時候發(fā)現(xiàn)超過1s了愉择,就會執(zhí)行NSTimer织中,這時候NSTimer就不準了狭吼,晚了0.3s。
所以如果想讓定時器準確的執(zhí)行任務,最好使用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.
//創(chuàng)建一個定時器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//設置事件
/**
定時器設置
@param 定時器
@param 什么時候開始
@param 定時器延遲多久
@param 每隔幾秒執(zhí)行
@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
);
//設置定時器回調(diào),采用block的方式
// dispatch_source_set_event_handler(self.timer, ^{
// NSLog(@"定時器事件");
// });
//設置定時器回調(diào),采用 函數(shù)方式 _f 是function
dispatch_source_set_event_handler_f(self.timer, timerAction);
//啟動定時器
dispatch_resume(self.timer);
}
void timerAction(void *para){
NSLog(@"定時器事件");
}
@end
GCD的定時器是和系統(tǒng)內(nèi)核掛鉤的旧乞,所以就算界面上添加一個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();
// 創(chuàng)建定時器
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);
//對字典讀寫,加信號量鎖者蠕,保證創(chuàng)建任務和取消任務同時只有一個在做
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定時器的唯一標識
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 設置回調(diào)
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重復的任務
[self cancelTask:name];
}
});
// 啟動定時器
dispatch_resume(timer);
return name;
}
/**
封裝GCD定時器
@param target 消息發(fā)送者
@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 根據(jù)唯一標識取消任務
*/
+ (void)cancelTask:(NSString *)name
{
if (name.length == 0) return;
//對字典讀寫踱侣,加信號量鎖大磺,保證創(chuàng)建任務和取消任務同時只有一個在做
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對象/關聯(lián)對象/多線程/內(nèi)存管理/性能優(yōu)化杠愧,相關圖片素材均取自課程中的課件。如有侵權锐锣,請聯(lián)系我刪除绳瘟,謝謝!