前言
这里主要分析定时器与RunLoop、多线程之间的关系以及易错点。
定时器包含以下几部分:
- NSTimer
- CADisplayLink
- GCD
- performSelector:afterDelay
小注:
- 上面的定时器除了GCD,其它都是基于RunLoop的,也就是说如果在子线程(默认不开启RunLoop),所有定时器方法都不会执行的。
- GCD定时器精确到纳秒
- GCD并不运行在RunLoop中
- RunLoop底层采用GCD定时器
一、NSTimer
1.1、自动加入当前的RunLoop、模式是default mode。
但是子线程中,需要手动创建runloop,并进行创建
- scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
- scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
- scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
1.2、不会自动加入RunLoop、需要手动addTimer:forMode
- timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
- timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
- timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
- initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep;
- initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
易错题:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//不会执行
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
//添加这句、可以正常执行
//必须在下面,因为不添加Source,不会正常开启RunLoop
[[NSRunLoop currentRunLoop] run];
});
二、CADisplayLink
这里需要手动加入RunLoop
// 创建CADisplayLink
CADisplayLink *disLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkMethod)];
//触发间隔
disLink.frameInterval = 2;
// 添加至RunLoop中
[disLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
// 终止定时器
[disLink invalidate];
// 销毁对象
disLink = nil;
三、GCD
/** 创建定时器对象
* para1: DISPATCH_SOURCE_TYPE_TIMER 为定时器类型
* para2-3: 中间两个参数对定时器无用
* para4: 最后为在什么调度队列中使用
*/
_gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
/** 设置定时器
* para2: 任务开始时间
* para3: 任务的间隔
* para4: 可接受的误差时间,设置0即不允许出现误差
* Tips: 单位均为纳秒
*/
dispatch_source_set_timer(_gcdTimer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
/** 设置定时器任务
* 可以通过block方式
* 也可以通过C函数方式
*/
dispatch_source_set_event_handler(_gcdTimer, ^{
static int gcdIdx = 0;
NSLog(@"GCD Method: %d", gcdIdx++);
NSLog(@"%@", [NSThread currentThread]);
if(gcdIdx == 5) {
// 终止定时器
dispatch_suspend(_gcdTimer);
}
});
// 启动任务,GCD计时器创建后需要手动启动
dispatch_resume(_gcdTimer);
备注:
GCD不依赖RunLoop
四、performSelector:afterDelay
4.1、概念
- 是消息传递的一种方式
- 不需要编译的时候声明这些方法,是运行时
4.2、相关方法
4.2.1、同步执行、在任何线程正常执行(不受RunLoop影响)
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
4.2.2、异步执行、只在主线程执行(RunLoop影响)
原因就是子线程RunLoop默认不开启,定时器必须加入RunLoop才能正常执行。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray * )modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
4.2.3、主线程(waitUntilDone决定是否阻塞主线程)(不受RunLoop影响)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray * )array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
4.2.4、主线程和子线程(waitUntilDone决定是否阻塞主线程)(不受RunLoop影响)
- (void)performSelector:(SEL)aSelector onThread:(NSThread * )thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray * )array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread * )thr withObject:(id)arg waitUntilDone:(BOOL)wait;
总结:
1、不受Runloop、子线程影响,可以正常使用的
GCD
2、不受Runloop、受子线程影响(需要手动启动Runloop)
NSTimer➡️scheduledTimer...方法
3、受Runloop、受子线程影响(需要手动加入Runloop、需要手动启动)
NSTimer➡️除了scheduledTimer...方法
CADisplayLink
performSelector: afterDelay:
网友评论