先写一个小例子,然后再来深层析挖掘,本文有部分借鉴网上和官方现有文档
上代码
#import "ViewController.h" @interface ViewController () { NSTimer *_timer; int _i; } @property (weak, nonatomic) IBOutlet UILabel *lable; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _i = 0; } - (void)changeTimeAtTimedisplay { _lable.text = [NSString stringWithFormat:@"%d",_i]; _i ++; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (IBAction)open:(id)sender { //如果定时器对象不存在,则创建一个并启动 if(!_timer){ //创建一个定时器,这个是直接加到当前消息循环中,注意与其他初始化方法的区别 _timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES]; // [_timer fire]; //对于这个fire方法,稍后会详解,它不是启动一个定时器,这么简单 } } - (IBAction)shut:(id)sender { if (_timer) { //如果定时器在运行 if ([_timer isValid]) { [_timer invalidate]; _timer = nil; _i = 0; } } }
然后我们说一下注意事项
1、初始化
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
注:不用scheduled方式初始化的,需要手动addTimer:forMode:将timer添加到一个runloop中。
而scheduled的初始化方法将以默认mode直接添加到当前的runloop中.
第一种方法:
如果,我们想在任何时候都能够随心所欲的启动/停止定时。
使用timerWithTimeInterval方法来实例化一个NSTimer,这时候NSTimer是不会启动的
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(paint:) userInfo:nil repeats:YES];
当需要调用时,可以把计时器添加到事件处理循环中
[[NSRunLoop currentRunLoop] addTimer:self.paintingTimer forMode:NSDefaultRunLoopMode];
问题来了,NSRunLoop是什么?
NSRunLoop 循环运行!就像是程序的中枢神经,一直在运转着,并且监视着程序各种的事件、线程等等。
一旦出现了相关的事件,那么就开始调度,分配适当的对象来执行适当的操作!
调用计时器方法:
Interval间隔时间 执行方法selector是否重复repeats
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerWay) userInfo:nil repeats:YES];
注意:将计数器的repeats设置为YES的时候,self的引用计数会加1。
因此可能会导致self(即viewController)不能release,所以,必须在viewWillAppear的时候,将计数器timer停止,否则可能会导致内存泄露。
停止timer的运行,但这个是永久的停止:(注意:停止后,一定要将timer赋空,否则还是没有释放)
取消定时器
[_timer invalidate]; _timer = nil;
要想实现:先停止,然后再某种情况下再次开启运行timer,可以使用下面的方法:
首先关闭定时器不能使用上面的方法,应该使用下面的方法:
关闭定时器
[_timer setFireDate:[NSDate distantFuture]];
开启这个timer:
开启定时器
[_timer setFireDate:[NSDate distantPast]];
例子:比如,在页面消失的时候关闭定时器,然后等页面再次打开的时候,又开启定时器。
(主要是为了防止它在后台运行,暂用CPU)可以使用下面的代码实现:
页面将要进入前台,开启定时器 -(void)viewWillAppear:(BOOL)animated { //开启定时器 [_timer setFireDate:[NSDate distantPast]]; } //页面消失,进入后台不显示该页面,关闭定时器 -(void)viewDidDisappear:(BOOL)animated { //关闭定时器 [_timer setFireDate:[NSDate distantFuture]]; }
官方
一、什么是NSTimer
官方给出解释是“A timer provides a way to perform a delayed action or a periodic action. The timer waits until a certain time interval has elapsed and then fires, sending a specified message to a specified object. ” 翻译过来就是timer就是一个能在从现在开始的后面的某一个时刻或者周期性的执行我们指定的方法的对象。
二、NSTimer和它调用的函数对象间到底发生了什么
从前面官方给出的解释可以看出timer会在未来的某个时刻执行一次或者多次我们指定的方法,这也就牵扯出一个问题,如何保证timer在未来的某个时刻触发指定事件的时候,我们指定的方法是有效的呢?
解决方法很简单,只要将指定给timer的方法的接收者retain一份就搞定了,实际上系统也是这样做的。不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显示的invalidate它为止”。
三、NSTimer会是准时触发事件吗
答案是否定的,而且有时候你会发现实际的触发时间跟你想象的差距还比较大。NSTimer不是一个实时系统,因此不管是一次性的还是周期性的,timer的实际触发事件的时间可能都会跟我们预想的会有出入。差距的大小跟当前我们程序的执行情况有关系,比如可能程序是多线程的,而你的timer只是添加在某一个线程的runloop的某一种指定的runloopmode中,由于多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化。
假设你添加了一个timer指定2秒后触发某一个事件,但是签好那个时候当前线程在执行一个连续运算(例如大数据块的处理等),这个时候timer就会延迟到该连续运算执行完以后才会执行。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会和后面的触发进行合并,即在一个周期内只会触发一次。但是不管该timer的触发时间延迟的有多离谱,他后面的timer的触发时间总是倍数于第一次添加timer的间隙。
原文如下“A repeating timer reschedules itself based on the scheduled firing time, not the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.”
四、NSTimer为什么要添加到RunLoop中才会有作用
前面的例子中我们使用的是一种便利方法,它其实是做了两件事:首先创建一个timer,然后将该timer添加到当前runloop的default mode中。也就是这个便利方法给我们造成了只要创建了timer就可以生效的错觉,我们当然可以自己创建timer,然后手动的把它添加到指定runloop的指定mode中去。
NSTimer其实也是一种资源,如果看过多线程编程指引文档的话,我们会发现所有的source如果要起作用,就得加到runloop中去。同理timer这种资源要想起作用,那肯定也需要加到runloop中才会生效喽。如果一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。你可能会说那我们APP的主线程的runloop我们没有往其中添加任何资源,为什么它还好好的运行。我们不添加,不代表框架没有添加,如果有兴趣的话你可以打印一下main thread的runloop,你会发现有很多资源。
五、NSTimer加到了RunLoop中但迟迟的不触发事件
为什么明明添加了,但是就是不按照预先的逻辑触发事件呢???原因主要有以下两个:
1、runloop是否运行
每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动。
2、mode是否正确
我们前面自己动手添加runloop的时候,可以看到有一个参数runloopMode,这个参数是干嘛的呢?
前面提到了要想timer生效,我们就得把它添加到指定runloop的指定mode中去,通常是主线程的defalut mode。但有时我们这样做了,却仍然发现timer还是没有触发事件。这是为什么呢?
这是因为timer添加的时候,我们需要指定一个mode,因为同一线程的runloop在运行的时候,任意时刻只能处于一种mode。所以只能当程序处于这种mode的时候,timer才能得到触发事件的机会。
本文Demo:http://download.csdn.net/detail/jackjia2015/9419952