iOS 中想实现方法的延迟执行和定时器功能,方法有很多,各有所长,所以在开发者,我们应该选择合适的方法。
延迟执行:
- performSelector:afterDelay:
- NSTimer
- GCD的dispatch_after
定时器:
- NSTimer
- GCD的dispatch_source_t timer
一、延迟执行
01 - 使用performSelector:afterDelay:方式实现
官方:
// 默认只在主线程中执行,因为它默认添加到主runloop运行循环里面的NSDefaultRunLoopMode模式内
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
// 默认只在主线程中执行,但可以选择其它多种的运行循环模式,包括:NSDefaultRunLoopMode,NSRunLoopCommonModes(the default, modal, and event tracking modes)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
事例:
/**
* 延迟2秒后执行 doSomething 方法,受UI事件的影响,比如UIScrollView的拖动操作
*/
[self performSelector:@selector(doSomething) withObject:nil afterDelay:2.0];
/**
* 延迟2秒后执行 不受UI事件的影响
*/
[self performSelector:@selector(doSomething:) withObject:nil afterDelay:2.0 inModes:@[NSRunLoopCommonModes]];
02 - 使用NSTimer方式实现,设置repeats参数(是否重复)为NO
官方:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
事例:
// 01 手动添加到runloop运行循环中
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doSomething) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 02 由系统自动添加到运行循环中
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(doSomething) userInfo:nil repeats:NO];
解析:上面两种NSTimer的方法是一样的,01是手动将timer添加到普通的运行循环里面,02的scheduledTimerWithTimeInterval方式,系统默认将timer添加到Mode:NSDefaultRunLoopMode 模式中,所以当01的Mode为NSDefaultRunLoopMode,两者等效
但01方式创建timer更加灵活,它可以选择不同的运行模式,包含:NSRunLoopCommonModes
03 - 使用GCD的dispatch_after方式实现
官方:
dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
事例:
__weak typeof(self) weakSelf = self;
// 参数1:设置什么时候开始,默认从当前时间开始
// 参数2:设置时间间隔,多久后执行
// 参数3:设置在哪个线程执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf doSomething];
});
解析:利用GCD 的 dispatch_after 不需要考虑runloop的运行循环,同时还可以选择在哪个线程中执行操作
三者比较和总结
1.NSTimer 和 performSelector方法都是基于runloop的
2.如果在子线程使用 performSelector和scheduledTimerWithTimeInterval 将是无效的
主线程的runloop默认处于激活状态,如果想在子线程添加他们,需要自己创建子线程的runloop并手动启动
3.NSTimer 和 performSelector的创建与撤销必须在同一个线程操作
4.内存管理有潜在泄露的风险
scheduledTimerWithTimeInterval方法将target设为A对象时,A对象会持有这个timer,但同时,timer会被当前的runloop所持有,如果对象处理不当,可能会造成内存泄露。
当使用NSTimer的scheduledTimerWithTimeInterval方法时,事实上此时Timer会被加入到当前线程的Run Loop中,且模式是默认的NSDefaultRunLoopMode。
而如果当前线程就是主线程,也就是UI线程时,某些UI事件,比如UIScrollView的拖动操作,会将Run Loop切换成NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不会执行
所以为了设置一个不被UI干扰的Timer,我们需要手动创建一个Timer,然后使用NSRunLoop的addTimer:forMode:方法来把Timer按照指定模式加入到Run Loop中。这里使用的模式是:NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合
二、定时器
01 - 使用NSTimer方式实现,设置repeats参数(是否重复)为YES
实现方式和上面的用法一样,不在累述的
02 - 使用GCD的dispatch_source_t timer方式实现
__weak typeof(self) weakSelf = self;
// dispatch_queue_t queue = dispatch_get_main_queue(); 主线程
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 全局子线程
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
[weakSelf doSomething:timer];
});
// 启动timer
dispatch_resume(timer);
// 暂停timer
dispatch_suspend(timer);
// 取消timer
dispatch_source_cancel(timer);
使用注意:如果想要在dispatch_source_set_event_handler中实现对应的方法,必须引用block传过来的timer这个对象,否则无法执行.