ios 常用的定时器有三种:NSTime,CADisplayLink和GCD。
NsTimer
// 参数:Interval:时间间隔 userInfo:携带信息,用来传值 repeats:是否循环
//第一种,使用 timerWithTimeInterval:target:selector:userInfo:repeats: 方法
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doAnything) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//第二种,使用 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 方法
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(doSomething:) userInfo:@5 repeats:NO];
- (void)doSomething:(NSTimer *)timer{
NSString * str = [timer userInfo];
NSLog(@"%@",str);
}
- (void)doAnything{
NSLog(@"doAnything...");
}
//用第一种方式创建,必须手动将timer加入RunLoop,否则timer的事件是不会响应的,其中 NSDefaultRunLoopMode 代表RunLoop的模式;iOS对外开放的RunLoop Mode有两个;
// FOUNDATION_EXPORT NSRunLoopMode const NSDefaultRunLoopMode;默认的运行模式,用于大部分操作
// FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes NS_AVAILABLE(10_5, 2_0);//是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式。用这种模式,即可以处理页面类似于tableview和scrollerview的滑动事件 ,也可以同时保证定时器的运行
用第二种方法则默认加入RunLoop,且默认模式为NSDefaultRunLoopMode
// 取消、停止、开始 定时器
//关闭定时器
[myTimer setFireDate:[NSDate distantFuture]];
//开启定时器
[myTimer setFireDate:[NSDate distantPast]];
//取消定时器(用来取消循环执行的定时器,否则可以省略。取消之后要释放。将计数器的repeats设置为YES的时候,self的引用计数会加1。)
[timer invalidate];
timer = nil;
CADisplayLink
CADisplayLink是一个和屏幕刷新率同步的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息,CADisplayLink类对应的selector就会被调用一次,所以可以使用CADisplayLink做一些和屏幕操作相关的操作。相比于NSTimer,CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。但是因为iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。NSTimer则会因为现成阻塞,产生误差
// 创建displayLink
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(change)];
// 将创建的displaylink添加到runloop中,否则定时器不会执行
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 停止定时器
[displayLink invalidate];
displayLink = nil;
- (void)change {
_imageView.transform = CGAffineTransformRotate(_imageView.transform, M_PI / 100);
}
GCD定时器(精度很高,不受RunLoop的Mode影响)
一次性定时
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);
dispatch_after(timer, dispatch_get_main_queue(), ^(void){
NSLog(@"GCD-----%@",[NSThread currentThread]);
});
循环定时器
@property (nonatomic ,strong)dispatch_source_t timer;// 注意:此处应该使用强引用 strong
{
NSTimeInterval period = 1.0; //设置时间间隔
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行
//要调用的任务
dispatch_source_set_event_handler(_timer, ^{ //在这里执行事件
[self doAnything];
});
//.开始执行
dispatch_resume(_timer);
//释放
self.timer = nil;
}
GCD定时器时间非常精准,最小的定时时间可以达到1纳秒,所以用在非常精确的定时场合。
RunLoop
RunLoop是线程相关的的基础框架的一部分。一个run loop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。其实内部就是do-while循环,这个循环内部不断地处理各种任务(比 如Source,Timer,Observer)。使用run loop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。
RunLoop是基于线程而存在的,每条线程都有唯一的一个与之对应的RunLoop对象,主线程的RunLoop已经自动创建和启动,子线程的RunLoop需要主动创建、调用run方法启动。RunLoop在第一次获取时创建,在线程结束时销毁
Core Foundation中包含了关于RunLoop的5个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef的接口进行了封装,代表RunLoop的运行模式
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。
系统默认注册了5个Mode:
NSDefaultRunLoopMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
NSRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
CFRunLoopModeRef 类并没有对外暴露,苹果对外暴露的Mode也只有两个;NSDefaultRunLoopMode和NSRunLoopCommonModes,NSRunLoopCommonModes更像是一个模式集合。一个 Mode 可以将自己标记为"Common"属性(通过将其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 标记的所有Mode里。
假如存在这样一个页面,tableview的headerView是一个轮播图,当tableview被拖动时,轮播图自动播放图片的定时器失效。
通常添加定时器到RunLoop里面,如果选择的Mode为NSDefaultRunLoopMode,那么当tableview拖动时,便会邮NSDefaultRunLoopMode 切换为UITrackingRunLoopMode,如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入,所以在滑动的时候定时器失效;这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响,所以如果把在[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];的Mode换成NSRunLoopCommonModes,在定时器滑动时就不会对定时器产生影响;NSRunLoopCommonModes更像是是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式。(每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 标记的所有Mode里)
CFRunLoopSourceRef
CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
Source0 非基于Port的
,只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
基于Port的,通过内核和其他线程通信,接收、分发系统事件
CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器,CFRunLoopTimerRef基本上说的就是NSTimer,它受RunLoop的Mode影响,GCD的定时器不受RunLoop的Mode影响
CFRunLoopObserverRef
CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变