使用timer需要注意的问题:
timer 会retain 你添加调用方法的对象
timer需要添加到runLoop中才会起作用
timer不是准确的按指定的时间执行定义的方法
timer添加到runLoop中时的模式选择
1.什么是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就是一个能在从现在开始的后面的某一个时刻或者周期性的执行我们指定的方法的对象。
2.NSTimer和它调用的函数对象间到底发生了什么
从前面官方给出的解释可以看出timer会在未来的某个时刻执行一次或者多次我们指定的方法,这也就牵扯出一个问题,如何保证timer在未来的某个时刻触发指定事件的时候,我们指定的方法是有效的呢?
解决方法很简单,只要将指定给timer的方法的接收者retain一份就搞定了,实际上系统也是这样做的。不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显示的invalidate它为止”。
3.在timer引用对象的dealloc 中释放timer
以下这种timer的用法,企图在dealloc中对timer进行invalidate是一种自欺欺人的做法
因为你的timer对self进行了retain,如果timer一直有效,则self的引用计数永远不会等于0, 就不会走dealloc方法;
4.NSTimer会是准时触发事件吗
答案是否定的,而且有时候你会发现实际的触发时间跟你想象的差距还比较大。NSTimer不是一个实时系统,因此不管是一次性的还是周期性的timer的实际触发事件的时间可能都会跟我们预想的会有出入。差距的大小跟当前我们程序的执行情况有关系,比如可能程序是多线程的,而你的timer只是添加在某一个线程的runloop的某一种指定的runloopmode中,由于多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化。
假设你添加了一个timer指定2秒后触发某一个事件,但是签好那个时候当前线程在执行一个连续运算(例如大数据块的处理等),这个时候timer就会延迟到该连续运算执行完以后才会执行。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会和后面的触发进行合并,即在一个周期内只会触发一次。但是不管该timer的触发时间延迟的有多离谱,他后面的timer的触发时间总是倍数于第一次添加timer的间隙。
5.NSTimer为什么要添加到RunLoop中才会有作用
前面的例子中我们使用的是一种便利方法,它其实是做了两件事:首先创建一个timer,然后将该timer添加到当前runloop的default mode中。也就是这个便利方法给我们造成了只要创建了timer就可以生效的错觉,我们当然可以自己创建timer,然后手动的把它添加到指定runloop的指定mode中去。
NSTimer其实也是一种资源,如果看过多线程变成指引文档的话,我们会发现所有的source如果要起作用,就得加到runloop中去。同理timer这种资源要想起作用,那肯定也需要加到runloop中才会又效喽。如果一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。你可能会说那我们APP的主线程的runloop我们没有往其中添加任何资源,为什么它还好好的运行。我们不添加,不代表框架没有添加,如果有兴趣的话你可以打印一下main thread的runloop,你会发现有很多资源。
6.用block的方式解决循环引用
给NSTimer添加扩展
@interface NSTimer (BlockSupport)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(NSTimer * timer))block;
@end
@implementation NSTimer (BlockSupport)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(NSTimer * timer))block {
NSTimer *timer = [self timerWithTimeInterval:interval target:self selector:@selector(trigger:) userInfo:[block copy] repeats:YES];
return timer;
}
+ (void)trigger:(NSTimer *)timerSender {
void (^block)(NSTimer *timer) = [timerSender userInfo];
if (block) {
block(timerSender);
}
}
@end
使用方法:
@property (nonatomic, strong) NSTimer *timer;
// 防止block捕获self变量
__weak typeof(self) weakSelf = self;
_menuShowAndHideTimer = [NSTimer timerWithTimeInterval:2 block:^(NSTimer *timer) {
[weakSelf timeCount];
}];
// 主线程时,为[NSRunLoop mainRunLoop];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
// 注意模式的选择
[currentRunLoop addTimer:_menuShowAndHideTimer forMode:NSDefaultRunLoopMode];
// 主线程是不需要执行,默认开启
[currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0]];