用GCD实现Timer

iOS延迟的操作有三种:

NSObjectperformSelector afterDelay
NSTimer
GCD 的dispatch_after
三种方法都有各自的优缺点:
NSTimerperformSelector

缺点:
  1. 必须保证一个活跃的runloop,子线程中因为不会自动开启runloop,所以需要手动激活或把它放到MainRunLoop里。
  2. NSTimer的创建与撤销必须在同一个线程中,performSelector 的创建和撤销必须在同一个线程中。
  3. 内存管理有泄漏风险。

Why?

timerperformSelector 会持有target。当
当一个timerschedule时候,timer会持有targetNSRunLoop会持有timer,当invalidate被调用,NSRunLoop会释放timer的持有,timer会释放对targert的持有,其它没有方法可以释放timertarget持有。所以解决内存问题必须手动释放timer

注:但是如果我们自己写一个业务逻辑里面有timer,我们希望内部逻辑具有自完备性,即不需要外部手动调用invalidate,逻辑上说外部也不需要知道这个业务是否使用了timer

dispatch_after

缺点:
一旦执行,不能撤销。而performSelector可以用
canclePreviousPerformRequestWithTarget撤销,timer可以用invalidate撤销

解决方案
为了解决以上的问题我们可以用GCD实现一个timer,系统会帮我们处理线程逻辑优化线程,而且不需要关心runloop问题,且调用对象不回被强行持有,只需要注意block中的循环引用就可。
具体的代码如下:


//1.取到queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.创建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.设置timer首次执行时间,间隔,精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//执行timer
__weak __typeof(self)weakSelf = self;
dispatch_source_set_event_handler(timer, ^{
[weakSelf doSomeThing];
});
//4.激活timer
dispatch_resume(timer);
//5.取消timer
dispatch_source_cancel(timer);

用这个方式可以解决timer的三个缺点,但是

  1. GCD timer的API太多 。
  2. 没有repeats,如果只想执行一次自己得写标志位控制。
    这些我们可以统一封装成河timer类似的api。

开始封装:


//设置一个timerContainer容器捕获所有timer,key:timerName ,value:timer
+ (instancetype)scheduledDispatchTimerWithName:(NSString *)timerName
                          timeInterval:(NSTimeInterval)interval
                                 queue:(dispatch_queue_t)queue
                               repeats:(BOOL)repeats
                                action:(dispatch_block_t)action {
    
    NSParameterAssert(timerName);
    GCDTimer *gcdTimer = [[GCDTimer alloc] init];
    if (!queue) {
        queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    dispatch_source_t timer = [gcdTimer.timerContainer objectForKey:timerName];
    if (!timer) {
        timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        [gcdTimer.timerContainer setObject:timer forKey:timerName];
        dispatch_resume(timer);
    }
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
    __weak GCDTimer *weakTimer = gcdTimer;
    dispatch_source_set_event_handler(timer, ^{
        action();
        if (!repeats) {
            [weakTimer cancelTimerWithName:timerName];
        }
    });
    return gcdTimer;
}

- (void)cancelTimerWithName:(NSString *)timerName {
    dispatch_source_t timer = [self.timerContainer objectForKey:timerName];
    if (!timer) {
        return ;
    }
    [self.timerContainer removeObjectForKey:timerName];
    dispatch_source_cancel(timer);
}

外部调用:

//1.外部开始timer
GCDTimer *gcdtimer = [GCDTimer scheduledDispatchTimerWithName:@"myTime"
                                                 timeInterval:2
                                                        queue:dispatch_get_main_queue()
                                                      repeats:YES
                                                       action:^{
                                                           NSLog(@"GCDTimer");
                                                       }];
//2.外部cancelTimer
[gcdtimer cancelTimerWithName@"myTime"];

上面我们弄了个timer,2s内执行一次,在主线程,重复执行,直到你cancel到这个timer,如果repeat为No,那么执行一次就会cancel掉这个timer.

  1. 通过queue我们可以让timer在不同的线程中执行,queue传nil默认放入一个子线程中进行。
  2. 我们可以在dealloc中做cancel,这样保证了timer运行于对象的整个生命周期

这些都是timerperformSelector不具有的优势。

外部调用简单明了,不需要关心timer的释放问题,和所在的线程是不是开启了runloop,只要注意一下循环引用,也没有了需要内存管理的风险。最后来波传送门GCDTimer

你可能感兴趣的:(用GCD实现Timer)