原文链接
你可以使用的定时器类来创建一个定时器对象,或者更简单地说,定时器。一个定时器在指定的时间到达后触发,并发送指定的消息到目标对象。例如你可以通过定时器来每间隔一段时间通知窗口刷新显示。
定时器常和运行循环一起使用。要有效的使用定时器,你需要注意运行循环的相关知识,可以参考NSRunLoop和Threading Programming指南。特别要注意的是,运行循环对拥有的定时器进行强引用,所以如果定时器已经加入了运行循环,那么你自己不需要再强引用这个定时器。
定时器并不是实时机制;只有在定时器所在的运行循环的那个模式正在运行且能够检测时间间隔是否已经达到的情况下才会被触发。因为一个典型的运行循环管理各种输入源,所以定时器的时间间隔的有效分辨率被限制为50-100毫秒的数量级。如果定时器的触发时间恰好处于运行循环外调期间,或者恰好处于运行循环不监控定时器的模式下,则定时器直到下个时间间隔才会被检查触发。另请参考Timer Tolerance(定时器公差)。
定时器是一个“toll-free bridged”,其Core Foundation副本CFRunLoopTimerRef。请参考Toll-Free Bridging来获得更多关于toll-free bridging的信息。
重复VS非重复计时器
你可以在创建的时候指定定时器是否重复。非重复的计时器触发一次后自动自动失效,防止计时器再次被触发。相比之下,重复定时器触发后会重新加入到相同的运行循环中。
重复计时器总是基于预定时间而不是实际时间来安排下次触发。例如一个定时器设定每5秒钟触发一次,预定触发时间将总是在原触发时间后的5s,即使实际的触发时间被延迟。如果触发时间延迟了一个或多个时间间隔,则在这段时间内只触发一次;触发后重新安排下次触发时间。
时间公差
在iOS7和更高版本以及OS X v10.9和更高版本,您可以指定计时器的公差。允许在使用定时器增强系统的能力来优化节能和增加响应性方面的灵活性。可能在设定时间和设定时间加上公差时间之内的任何时间触发。计时器不会在预定的触发时间前触发。对于重复定时器,下一个触发时间总是基于原始触发时间来计算的而不是计入公差触发时间,这样可以防止偏移。 默认值是0即无公差 。对于某些计时器,在用户设置的公差范围外,系统保留适量的公差。
作为定时器的使用者,你自己最清楚一个定时器可以允许的公差应该设置为多少。根据一般经验规则,对于重复定时器,公差一般设置为间隔的至少10%. 即使少量的公差也会对系统电量使用有显著积极的影响。系统可能使用公差的最大值。
在运行循环中调度计时器
一个定时器对象同时只能被加入到一个运行循环中,虽然它在那个运行循环内可以被添加到多个运行循环模式中。有三种方法来创建一个定时器:
通过 scheduledTimerWithTimeInterval:invocation:repeats:或者scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class方法来创建定时器并将定时器加入到当前运行循环的默认模式中。
通过timerWithTimeInterval:invocation:repeats:或者timerWithTimeInterval:target:selector:userInfo:repeats: class方法创建一个定时器对象,不加入任何运行循环。(创建完之后,你必须通过调用 NSRunLoop 对象的addTimer:forMode:方法手动将定时器加入一个运行循环)
通过initWithFireDate:interval:target:selector:userInfo:repeats:方法创建并初始化一个定时器. (创建完之后,你必须通过调用 NSRunLoop 对象的addTimer:forMode:方法手动将定时器加入一个运行循环)
一旦定时器加入到了运行循环中,定时器将在指定的时间触发,直到定时器失效。一个非重复的定时器在触发后将会自动失效。而对于重复的定时器,你必须通过调用定时器对象的取消方法来将其取消。
调用这个方法需要讲定时器从当前的运行循环中移除; 所以, 你应当在创建定时器的线程中调用这个取消的方法。 取消的方法将使定时器立即失效以不再影响运行循环。运行循环接着移除定时器(同时移除对定时器的强引用) ,无论是在取消的方法刚刚返回或者是返回后的某个时刻。一旦取消,定时器对象不能再被使用。
一个重复的定时器被触发后,会讲下一次触发安排在一个整数乘以时间间隔后最靠近这次触发时间的时间点,并在允许的公差范围内。
如果执行selector或者函数调用的时间比设定的时间间隔长,定时器将只在下个时间间隔触发;也就是说,计时器不会弥补执行selector或者函数调用期间所丢失的触发。
子类注意事项
你不应该试图继承定时器类.
创建一个定时器
- scheduledTimerWithTimeInterval:invocation:repeats:
- scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
- timerWithTimeInterval:invocation:repeats:
- timerWithTimeInterval:target:selector:userInfo:repeats:
- initWithFireDate:interval:target:selector:userInfo:repeats:
Designated Initializer
触发定时器
- fire
关闭定时器
- invalidate
定时器相关函数
valid
fireDate
timeInterval
userInfo
Firing Tolerance
实际使用中注意事项
创建一个定时器:
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:@selector(onTick:)
userInfo: nil repeats:NO];
- 如果repeats设为NO, 则定时器会等待2秒,然后调用selector ,之后再关闭定时器;
- 如果repeats设为YES, 则定时器会立即调用selector,之后每隔2秒调用一次selector;要关闭定时器需要调用 [t invalidate];
- 以上方法创建的定时器,默认是加入到主线程的NSDefaultRunLoopMode模式中。当主线程处理某些UI事件时,比如UIScrollView的拖动操作,主线程将会切换成NSEventTrackingRunLoopMode模式,这个模式不会处理NSDefaultRunLoopMode模式中注册的事件。也就是说此时这个Timer就不会执行,直到主线程恢复到NSDefaultRunLoopMode模式。若想要主线程处理UI时不影响Timer的执行,可讲Timer加入到NSRunLoopCommonModes模式。这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合。另一种方法,将Timer加入到非主线程的运行循环中,则主线程中的操作影响不到Timer的执行。若Timer中需要UI操作,可切换回主线程进行UI操作。
- 另外一个选择,你可以通过这样简单的语句来替代非重复的定时器:
[self performSelector:@selector(onTick:) withObject:nil afterDelay:2.0];
指定时间间隔后的重复定时器
NSDate *d = [NSDate dateWithTimeIntervalSinceNow: 60.0];
NSTimer *t = [[NSTimer alloc] initWithFireDate: d
interval: 1
target: self
selector:@selector(onTick:)
userInfo:nil repeats:YES];
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer:t forMode: NSDefaultRunLoopMode];
[t release];
以上定时器在60秒后第一次触发,之后每隔1秒触发一次。
任意时间启动重复定时器
NSMethodSignature *sgn = [self methodSignatureForSelector:@selector(onTick:)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature: sgn];
[inv setTarget: self];
[inv setSelector:@selector(onTick:)];
NSTimer *t = [NSTimer timerWithTimeInterval: 1.0
invocation:inv
repeats:YES];
这之后你可以通过如下方法在任意时间启动定时器:
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer: t forMode: NSDefaultRunLoopMode];
onTick函数可以这样写:
-(void)onTick:(NSTimer *)timer {
//do smth
}