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
中止的问题,由于列表滚动的时候runloop
的mode
切换了,须要咱们手动将timer
的runloop
切换为commonMode
.github
3.
咱们常常会为使用NSTimer
出现的内存泄漏而烦恼,即便有解决方案,总感受很别扭.api
为何会内存泄漏呢?咱们看一下api
的描述markdown
timer
会一直强引用target
,直到timer
调用invalide
方法,在[timer invalidate]
调用以前,timer
和vc
构成了循环引用
的关系,因此在咱们vc
在退出当前页面的时候dealloc
方法并不调用,因此不论咱们的timer
是strong
仍是weak
都无济于事,由于api
内部会强持有。app
网上有不少种方案去实现打破这种循环引用来解决内存泄漏问题,这里咱们从自定义timer
提及,自定义timer说到底就是用dispatch_source
这套api
去实现.异步
Dispatch Source Timer
是一种与dispatch queue
结合使用的定时器,当须要在后台queue
中按期执行任务的时候,使用dispatch source timer
要比使用NSTimer
更天然,更高效(由于无需再main queue
和 异步queue
之间切换)。ide
官方文档提供建立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_walltime
,dispatch_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
中去才能执行.
开启timer的方法:
dispatch_resume
中止Dispatch Timer有两种方法:
dispatch_suspend
:只是暂时把timer
挂起,须要和dispatch_resume
配对使用,在挂起期间,产生的事件会积累,等到resume
的时候会整合为一个事件发送.dispatch_source_cancel
:至关于NSTimer
的invalidate
.结合上述的理解,咱们尝试自定义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
方法。还请不吝赐教和点赞支持,谢谢。