程序员社区

OC-使用GCD封装定时器

OC-使用GCD封装定时器

OC-使用GCD封装定时器插图
image-20210525124406608

NSTimerCADisplayLink实际上这两个计时器并不是一定准时的,因为他们都依赖于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对象/关联对象/多线程/内存管理/性能优化,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!

赞(0) 打赏
未经允许不得转载:IDEA激活码 » OC-使用GCD封装定时器

一个分享Java & Python知识的社区