GCD定时器

2019-12-17  本文已影响0人  小心韩国人

我们上次在内存管理一:NSTimer讲了NSTimerCADisplayLink的循环引用的问题.实际上这两个计时器并不是一定准时的,因为他们都依赖于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

源码在这里

上一篇下一篇

猜你喜欢

热点阅读