在iOS开发中我们经常会遇到一些业务,需要延迟一段时间去做一件事,或者是每隔一段时间都去做一件事,这就需要用到定时任务处理。iOS开发中常用的定时任务实现方式如下:
- performSelector 实现延时任务
- sleep(10)线程挂起/[NSThread sleepForTimeInterval: 10]实现任务等待,会阻塞主线程
- GCD的dispatch_after实现延时或dispatch_source_set_timer定时任务
- NSTimer实现定时任务
- CADisplayLink实现定时任务(帧频刷新)
一、performSelector
延时任务可以通过当前UIViewController的performSelector隐式创建子线程实现,不会阻塞住主线程。
[self performSelector:@selector(doSomething) withObject:nil afterDelay:10];
- (void)doSomething {
NSLog(@"%s",__func__);
}
一、sleep/sleepForTimeInterval
使用它们可以实现后面任务的等待,但是会阻塞主线程,还是慎用。
//C语言挂起线程10秒
sleep(10);
//Objective-C的线程类方法
[NSThread sleepForTimeInterval:10];
三、GCD(dispatch_after/dispatch_source_set_timer)
dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_main_queue(), ^{
// 2.0秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
GCD还可以用来实现定时器功能,还能设置延时开启计时器,使用中注意一定定义强引用指针来指向计时器对象才可以让计时器生效。
@property (nonatomic,strong) dispatch_source_t timer;
//在指定线程上定义计时器
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//开始的时间
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
//设置计时器
dispatch_source_set_timer(_timer, when, 1.0 * NSEC_PER_SEC, 0);
//计时器回调 block
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"dispatch_source_set_timer is working");
});
//开启计时器
dispatch_resume(_timer);
//强引用计时器对象
self.timer = _timer;
四、NSTimer
NSTimer主要用于开启定时任务,但要正确使用才能保证它能够正常有效地运行。
尤其要注意以下几点:
- 确保NSTimer已添加到当前Runloop,且当前runloop已启动;
- 主线程的runloop默认开启的,只要timer添加到runloop就会执行;
- 子线程的runloop默认关闭的,timer添加到runloop后要手动启动runloop ;
- NSTimer建议在子线程用,因为主线程要更新Ui影响时间准确性;
- scheduledTimerWithTimeInterval创建的timer会默认添加到当前RunLoop中;
- timerWithTimeInterval创建的timer需要手动添加到线程当前RunLoop中。
//timerWithTimeInterval:主线程创建timer,需要手动添加到runloop中
NSTimer *mainThreadTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(mainThreadTimer_SEL) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:mainThreadTimer forMode:NSDefaultRunLoopMode];
//scheduledTimerWithTimeInterval:主线程中创建timer,不需要手动添加到runloop中
NSTimer *mainThreadSchduledTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(mainThreadSchduledTimer_SEL) userInfo:nil repeats:YES];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//timerWithTimeInterval:子线程创建timer,需要手动添加到runloop中,并启动runloop
NSTimer *subThreadTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(subThreadTimer_SEL) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:subThreadTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
//scheduledTimerWithTimeInterval:子线程创建timer,不需要手动添加到runloop中,但需要启动runloop
NSTimer *subThreadSchduledTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(subThreadSchduledTimer_SEL) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
五、CADisplayLink
CADisplayLink实现的定时器与屏幕刷新频率绑定在一起,是一种帧频刷新,适用于界面的不断重绘(例如流畅动画和视频播放等)。
CADisplayLink需添加到当前运行的runloop中启动,默认每帧都调用,可根据需求设置每几帧调用一次。
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLink_SEL)];
//添加到当前运行的runloop中启动
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//暂停、继续对selector的调用
[displayLink setPaused:YES];
[displayLink setPaused:NO];
//设置每秒的帧数,默认为0,60帧每秒,有效范围1-60
[displayLink setPreferredFramesPerSecond:10];
//移除,不再调用
[displayLink invalidate];
displayLink = nil;
参考:《iOS程序员面试笔试宝典》