iOS延迟的操作有三种:
NSObject的performSelector afterDelay
NSTimer
GCD 的dispatch_after
三种方法都有各自的优缺点:
NSTimer 和 performSelector
缺点:
- 必须保证一个活跃的runloop,子线程中因为不会自动开启runloop,所以需要手动激活或把它放到MainRunLoop里。
- NSTimer的创建与撤销必须在同一个线程中,performSelector 的创建和撤销必须在同一个线程中。
- 内存管理有泄漏风险。
Why?
timer和performSelector 会持有target。当
当一个timer被schedule时候,timer会持有target,NSRunLoop会持有timer,当invalidate被调用,NSRunLoop会释放timer的持有,timer会释放对targert的持有,其它没有方法可以释放timer对target持有。所以解决内存问题必须手动释放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的三个缺点,但是
- GCD timer的API太多 。
- 没有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.
- 通过queue我们可以让timer在不同的线程中执行,queue传nil默认放入一个子线程中进行。
- 我们可以在dealloc中做cancel,这样保证了timer运行于对象的整个生命周期
这些都是timer和performSelector不具有的优势。
外部调用简单明了,不需要关心timer的释放问题,和所在的线程是不是开启了runloop,只要注意一下循环引用,也没有了需要内存管理的风险。最后来波传送门GCDTimer