NSTimer应用
NSTimer应用
在参与项目开发中遇到了NSTimer的应用,虽然我负责的模块内只用到了一小部分,但我觉得还是有必要拿出来好好琢磨一下。
一、概念(来自官方描述)
官网上最新的定义是“A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.” 也就是经过一定时间间隔后触发,向目标对象发送指定消息的计时器。
1、概述
定时器和run loops协同工作。而且run loops保持对定时器的强引用,所以不需要维护添加到run loops后的定时器。同时为了高效使用计时器,必了解run loops如何运行的。
计时器不是一个实时生效的机制。如果定时器启动时刻正一个很长时间的callout或者run loop处于一个不再检测这个定时的模式,它将不启动直到run loop检测到下一个启动时刻。所以定时器真正开始生效的时间是在程序设定启动之后的一段时间内。
NSTimer 是“tool-free bridge”模式,在Core Foundation中对应着CFRunLoopTimerRef。(几个专业名词对我来说真的有些陌生,小菜鸡真的还有很多要学习……)
2、重复和非重复定时器
在初始化定时器时,你需要明确这个定时器是否重复。 一个非重复的定时器触发一次后自动失效,从而阻止了定时器的再次触发。相反的是一个重复的定时器触发结束后重新把自己放到run loops中,准备下一次启动。重复计时器总是根据预定的触发时间来调度自己,而不是实际的触发时间。例如,如果一个定时器预定在某个特定的时间触发,每5秒之后,预定的触发时间将永远落在原来的5秒时间间隔内,哪怕是实际发射时间被推迟了。如果触发时间被延迟到通过一个或多个预定的触发间隔,则定时器仅在该时间内发射一次;然后触发后定时器被重新调度,为了未来预定下一次触发。
3、定时器的容差
在iOS 7 以及之后的版本和macOS 10.9以及之后的版本,你能指定定时器的容差。在定时器触发时,这个灵活性提高了系统的优化能力,以提高功耗和响应能力。定时器可能在任何预定的触发时间和预定加上容差的时间内触发,不会在预定的触发时间前触发。对于重复定时器,下一个触发时间是从初始的触发时间计算出来的,不管在单个触发时间内的容差,以免漂移。默认值是0,则意味着不使用额外的的容差。系统保留对部分定时器应用少量容差的权利,而不管容差的特性。
作为定时器的使用者,你能决定定时器的适当容差。一条普遍的规则,将对至少10%的区间的容差设置为重复定时器。即使是少量的容错也会对应用程序的效率产生显著的积极影响。系统能强制执行容差的最大值。
4、在Run Loops里安置定时器
你能在一个唯一的run loops中注册一个定时器,尽管它可以被添加到run loops中的多个run loops模式中。这有三种新建定时器的方法:
·用方法 scheduledTimerWithTimeInterval:invocation:repeats: 或者
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 类方法来创建定时器,并在默认模式下被安置到当前的run loop上。
·用方法timerWithTimeInterval:invocation:repeats:
或者 timerWithTimeInterval:target:selector:userInfo:repeats:类方法来创建定时器对象,且无需在run loops上调度它。(创建后,必须手动安置到一个run loop里,通过调用NSRunLoop的函数
addTimer:forMode:
)
·开辟资源并初始化定时器用 initWithFireDate:interval:target:selector:userInfo:repeats:方法。(同样,你必须手动安置到一个run loop里通过调用NSRunLoop的函数
addTimer:forMode:
)
一旦在run loops上调度,定时器就在指定的的间隔中触发,直到它失效为止。非重复的定时器在触发结束后立刻无效。但对于重复定时器,必须通过调用它的无效方法来使定时器对象无效。调用这个方法要求从当前的run loops中删除定时器,因此你应该在安装定时器的同一线程中调用无效方法。无效的定时器立即禁用为了不让它再影响run loop。之后run loop将删除定时器(以及对应的强引用),或者在还原方法返回前或稍后的某个时间点。一旦失效,定时器对象将不能被重用。
在重复定时器触发后,它将调度最近间隔的的下一个触发,即在最后一次预定的触发时间内,在指定的容差范围内,定时器间隔的整倍数。如果调用执行选择器或者调用时间长于指定的间隔,则定时器只安排下一次触发。那是定时器为不试图弥补错过触发在调用指定的选择器或调用发生。
5、子类注意事项
不要尝试用NSTimer生成子类。
二、实例操作
我应用到NSTimer的开发部分是为了实现发送验证码状态转换的按钮,实现效果如右动图
首先要在设置“发送验证码”按钮时做好接口
1 [self.sendVerificationCode addTarget:self action:@selector(pressGainCode:) forControlEvents:UIControlEventTouchUpInside];
配置相关的方法(实例中我取名为pressGainCode),为了支持重复获取验证码的情况,我使用的是重复定时器。
1 - (void)pressGainCode:(UIButton *)btn { 2 [self validationCodeTimer]; 3 } 4 5 6 - (void)validationCodeTimer{ 7 __block int timeout = 60; //倒计时时间 8 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 9 _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue); 10 dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒执行 11 dispatch_source_set_event_handler(_timer, ^{ 12 if(timeout <= 0){ //倒计时结束,关闭 13 dispatch_source_cancel(_timer); 14 dispatch_async(dispatch_get_main_queue(), ^{ 15 self.sendVerificationCode.userInteractionEnabled = YES; 16 self.sendVerificationCode.enabled = YES; 17 [self.sendVerificationCode setTitle:@"重获验证码" forState:UIControlStateNormal]; 18 }); 19 } 20 else { 21 timeout--; 22 NSString *strTime = [NSString stringWithFormat:@"%d秒后重发",timeout]; 23 dispatch_async(dispatch_get_main_queue(), ^{ 24 self.sendVerificationCode.enabled = NO; 25 [self.sendVerificationCode setTitle:strTime forState:UIControlStateDisabled]; 26 }); 27 } 28 }); 29 dispatch_resume(_timer); 30 }
参考资料:Apple官方文档 、四个实用demo 、定时器总结 、官文翻译及基础操作