dispatch_source之自定义Timer

一、How about NSTimer?

NSTimer可能你们都熟悉,他的api也都很简单,可是其使用过程并不容易,相信用过的同窗都踩过坑.一般咱们这么用:ios

// 定义 
@property (nonatomic, strong) NSTimer *timer;
// 使用 
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(demo:) userInfo:nil repeats:YES];
复制代码

1. timerWithTimeInterval开头的方法须要本身添加到指定的runloop中去,而scheduledTimerWithTimeInterval开头的方法默认添加到当前的runloop中去.git

2. 默认添加到runloop中的NSTimer会以NSDefaultRunLoopMode的模式放入当前的runloop,这就致使常常出现的列表滚动timer中止的问题,由于列表滚动的时候runloopmode切换了,须要咱们手动将timerrunloop切换为commonMode.github

3.咱们常常会为使用NSTimer出现的内存泄漏而烦恼,即便有解决方案,总感受很别扭.api

为何会内存泄漏呢?咱们看一下api的描述markdown

timer会一直强引用target,直到timer调用invalide方法,在[timer invalidate]调用以前,timervc构成了循环引用的关系,因此在咱们vc在退出当前页面的时候dealloc方法并不调用,因此不论咱们的timerstrong仍是weak都无济于事,由于api内部会强持有。app

网上有不少种方案去实现打破这种循环引用来解决内存泄漏问题,这里咱们从自定义timer提及,自定义timer说到底就是用dispatch_source这套api去实现.异步

二、Dispatch Source Timer

Dispatch Source Timer 是一种与dispatch queue结合使用的定时器,当须要在后台queue中按期执行任务的时候,使用dispatch source timer 要比使用NSTimer更天然,更高效(由于无需再main queue 和 异步queue之间切换)。ide

2.1 建立timer

官方文档提供建立timer的示例是:oop

dispatch_source_t CreateDispatchTimer(uint64_t interval,
              uint64_t leeway,
              dispatch_queue_t queue,
              dispatch_block_t block)
{
   dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                                     0, 0, queue);
   if (timer)
   {
      dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
      dispatch_source_set_event_handler(timer, block);
      dispatch_resume(timer);
   }
   return timer;
}
复制代码

注意点:ui

1. 此处的timer是间隔定时器,每隔一段时间就会触发而不须要像NSTimer那样设值repeats.

2. dispatch_source_set_timer 中的第二个参数能够传入dispatch_time 或者 dispatch_walltimedispatch_time表示选择是默认钟表来计时,这个会随着系统休眠而休眠,而 dispatch_walltime 可让定时间按照实际时间一直运行下去,因此针对间隔时间比较久的定时器应采用dispatch_walltime,上述例子中的dispatch_walltime(NULL, 0) 等同于 dispatch_time(DISPATCH_WALLTIME_NOW, 0).

3. dispatch_source_set_timer 中的第四个参数leeway 表明的是指望容忍时间,若是设值为1,意味着定时器时间达到前一秒或者后一秒才真正触发定时器,计时指定leeway的值为0,系统也没法保证彻底精确的触发时间,只是尽量的知足这个需求.

4. dispatch_source_set_event_handler 的参数block表明的是定时器间隔要执行的任务,它是绑定在执行的queue上的,这个相比NSTimer要方便太多,因为NSTimer须要Runloop支持,NSTimer则须要手动添加到指定线程的runloop中去才能执行.

2.2开启 和 中止timer

开启timer的方法:

  • dispatch_resume

中止Dispatch Timer有两种方法:

  • dispatch_suspend :只是暂时把timer挂起,须要和dispatch_resume配对使用,在挂起期间,产生的事件会积累,等到resume的时候会整合为一个事件发送.
  • dispatch_source_cancel:至关于NSTimerinvalidate.

三、Custom Timer

结合上述的理解,咱们尝试自定义timer,一样咱们模仿NSTimer的接口,这样使用起来没有违和感也更方便:

@interface SSTimer : NSObject
 /// 同下面的方法,不过自动开始执行
+ (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
 /// 建立一个定时器并返回,可是并不会自动执行,须要手动调用resume方法
 /// - parameter: start 定时器启动时间
 /// - parameter: ti 间隔多久开始执行selector
 /// - parameter: s 执行的任务
 /// - parameter: ui 绑定信息
 /// - parameter: rep 是否重复
- (instancetype)initWithTimeInterval:(NSTimeInterval)start interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep;
 /// 扩充block
+ (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(SSTimer *timer))block;
 /// 启动
- (void)resume;
 /// 暂定
- (void)suspend;
 /// 关闭
- (void)invalidate;
@property (readonly) BOOL repeats;
@property (readonly) NSTimeInterval timeInterval;
@property (readonly, getter=isValid) BOOL valid;
@property (nullable, readonly, retain) id userInfo;
@end
复制代码

这里咱们提供了启动和暂停的功能,相比NSTimer要好用不少,同时也扩充了block的参数,虽然apple也提供了block的参数方法,可是须要在ios10以上的系统上才能使用。

#import "SSTimer.h"
 #define lock(...) \
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);\
    __VA_ARGS__;\
    dispatch_semaphore_signal(_semaphore);
 @implementation SSTimer {
    BOOL _valid;
    NSTimeInterval _timeInterval;
    BOOL _repeats;
    __weak id _target;
    SEL _selector;
    dispatch_source_t _timer;
    dispatch_semaphore_t _semaphore;
    id _userInfo;
    BOOL _running;
}
+ (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
    SSTimer *timer = [[SSTimer alloc] initWithTimeInterval:0 interval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo];
    [timer resume];
    return timer;
}
+ (SSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(SSTimer *timer))block {
    NSParameterAssert(block != nil);
    SSTimer *timer = [[SSTimer alloc] initWithTimeInterval:0 interval:interval target:self selector:@selector(ss_executeBlockFromTimer:) userInfo:[block copy] repeats:repeats];
    [timer resume];
    return timer;
}
- (instancetype)initWithTimeInterval:(NSTimeInterval)start interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep {
    self = [super init];
    if (self) {
        _valid = YES;
        _timeInterval = ti;
        _repeats = rep;
        _target = t;
        _selector = s;
        _userInfo = ui;
        _semaphore = dispatch_semaphore_create(1);
        __weak typeof(self) weakSelf = self;
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
        dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), ti * NSEC_PER_SEC, 0);
        dispatch_source_set_event_handler(_timer, ^{[weakSelf fire];});
    }
    return self;
}
- (void)fire {
    if (!_valid) {return;}
    lock(id target = _target;)
    if (!target) {
        [self invalidate];
    } else {
        // 执行selector
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [target performSelector:_selector withObject:self];
#pragma clang diagnostic pop
        if (!_repeats) {
            [self invalidate];
        }
    }
}
- (void)resume {
    if (_running) return;
    dispatch_resume(_timer);
    _running = YES;
}
- (void)suspend {
    if (!_running) return;
    dispatch_suspend(_timer);
    _running = NO;
}
- (void)invalidate {
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    if (_valid) {
        dispatch_source_cancel(_timer);
        _timer = NULL;
        _target = nil;
        _userInfo = nil;
        _valid = NO;
    }
    dispatch_semaphore_signal(_semaphore);
}
- (id)userInfo {
    lock(id ui = _userInfo) return ui;
}
- (BOOL)repeats {
    lock(BOOL re = _repeats) return re;
}
- (NSTimeInterval)timeInterval {
    lock(NSTimeInterval ti = _timeInterval) return ti;
}
- (BOOL)isValid {
    lock(BOOL va = _valid) return va;
}
- (void)dealloc {
    [self invalidate];
}
+ (void)ss_executeBlockFromTimer:(SSTimer *)aTimer {
    void (^block)(SSTimer *) = [aTimer userInfo];
    if (block) block(aTimer);
}
@end
复制代码

这里咱们使用了__weak id _target,这样咱们就内部就切断了这层循环引用问题,作到自主释放,外层使用方也没必要关心,也不容易出现错误和内存泄漏. 具体的代码在SSTimer下载,须要的能够前去查看,目前内部也只是定义在main queue上执行任务,后续会添加关于不一样queue上执行任务的timer方法。还请不吝赐教和点赞支持,谢谢。

相关文章
相关标签/搜索