美文网首页
OC-使用GCD封装定时器

OC-使用GCD封装定时器

作者: 蒋斌文 | 来源:发表于2021-05-25 12:55 被阅读0次

    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对象/关联对象/多线程/内存管理/性能优化,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!

    相关文章

      网友评论

          本文标题:OC-使用GCD封装定时器

          本文链接:https://www.haomeiwen.com/subject/irgqsltx.html