1.创建NSTimer
常用方法有
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats;
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats;
他们的区别:
scheduledTimerWithTimeInterval相比它的小伙伴们不仅仅是创建了NSTimer对象, 还把该对象加入到了当前的runloop中!
NSTimer只有被加入到runloop, 才会生效, 即NSTimer才会被真正执行
所以说, 如果你想使用timerWithTimeInterval或initWithFireDate的话, 需要使用NSRunloop的以下方法将NSTimer加入到runloop中
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。
每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,
但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动。
runloop会运行在不同的mode, 简单来说有以下两种mode
NSDefaultRunLoopMode, 默认的mode
UITrackingRunLoopMode, 当处理UI滚动操作时的mode
主线程默认运行在NSDefaultRunLoopMode
在UI滚动时, runloop运行在UITrackingRunLoopMode
如果addTimerforMode是NSDefaultRunLoopMode,那么在UITrackingRunLoopMode下定时器就不运行
要想在这些场景下仍然及时触发NSTimer那应该怎么办呢?
应该使用NSRunLoopCommonModes,包含以上2个mode
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
例子
//创建一个定时器,
_timer=[NSTimer timerWithTimeInterval:10 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES];
//手动加入到循环中
NSRunLoop *runloop=[NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSRunLoopCommonModes];
当然这个定时器会自动启动,只不多过了十秒之后,才触发
fire的作用是提前触发定时器 ,如果执行
_timer.fireDate = CFAbsoluteTimeGetCurrent() + timeInterval;
那么会立即开始定时
2.销毁NSTimer
是否可以将NSTimer置nil, 而让iOS系统帮我们销毁NSTimer呢?
答案是: 不可以
原因:为了保证timer在未来触发指定事件时指定方法是有效的,将指定方法的接收者retain了一份。(重复性还是一次性的timer都是)
如果在VC在创建的NSTimer,VC会和timer相互强引用,VC的dealloc方法不会执行
[_timer invalidate]; // 可以打破相互强引用,真正销毁NSTimer对象
_timer = nil; // 对象置nil是一种规范和习惯
所以这段代码不能放在VC的dealloc方法中,可以放在VC的ViewWillDisappear方法中
3.NSTimer会是准时触发事件吗
答案是否定的。
NSTimer不是一个实时系统,因此不管是一次性的还是周期性的timer的实际触发事件的时间可能都会跟我们预想的会有出入。差距的大小跟当前我们程序的执行情况有关系,比如可能程序是多线程的,而你的timer只是添加在某一个线程的runloop的某一种指定的runloopmode中,由于多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化。
假设你添加了一个timer指定2秒后触发某一个事件,但是签好那个时候当前线程在执行一个连续运算(例如大数据块的处理等),这个时候timer就会延迟到该连续运算执行完以后才会执行。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会和后面的触发进行合并,即在一个周期内只会触发一次。但是不管该timer的触发时间延迟的有多离谱,他后面的timer的触发时间总是倍数于第一次添加timer的间隙。
4.BlocksKit中为NSTimer 写的分类
@implementation NSTimer (BlocksKit)
+ (instancetype)bk_scheduleTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))block
{
NSTimer *timer = [self bk_timerWithTimeInterval:seconds repeats:repeats usingBlock:block];
[NSRunLoop.currentRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
return timer;
}
+ (instancetype)bk_timerWithTimeInterval:(NSTimeInterval)inSeconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))block
{
NSParameterAssert(block != nil);
CFAbsoluteTime seconds = fmax(inSeconds, 0.0001);
CFAbsoluteTime interval = repeats ? seconds : 0;
CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent() + seconds;
return (__bridge_transfer NSTimer *)CFRunLoopTimerCreateWithHandler(NULL, fireDate, interval, 0, 0, (void(^)(CFRunLoopTimerRef))block);
}