/* NSTimer.h Copyright (c) 1994-2015, Apple Inc. All rights reserved. */ #import <Foundation/NSObject.h> #import <Foundation/NSDate.h> NS_ASSUME_NONNULL_BEGIN @interface NSTimer : NSObject /** 这下面主要是一些构造方法*/ // Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.) // 建立一个定时器,可是么有添加到运行循环,咱们须要在建立定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; // Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode. // 建立一个timer并把它指定到一个默认的runloop模式中,而且在 TimeInterval时间后 启动定时器 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; // Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.) // 建立一个定时器,可是么有添加到运行循环,咱们须要在建立定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; // Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode. // 建立一个timer并把它指定到一个默认的runloop模式中,而且在 TimeInterval时间后 启动定时器 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; // 默认的初始化方法,(建立定时器后,手动添加到 运行循环,而且手动触发才会启动定时器) - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER; // You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived. // 启动 Timer 触发Target的方法调用可是并不会改变Timer的时间设置。 即 time没有到达到,Timer会当即启动调用方法且没有改变时间设置,当时间 time 到了的时候,Timer仍是会调用方法。 - (void)fire; // 这是设置定时器的启动时间,经常使用来管理定时器的启动与中止 @property (copy) NSDate *fireDate; // 启动定时器 timer.fireDate = [NSDate distantPast]; //中止定时器 timer.fireDate = [NSDate distantFuture]; // 开启 [time setFireDate:[NSDate distanPast]] // NSTimer 关闭 [time setFireDate:[NSDate distantFunture]] //继续。 [timer setFireDate:[NSDate date]]; // 这个是一个只读属性,获取定时器调用间隔时间 @property (readonly) NSTimeInterval timeInterval; // Setting a tolerance for a timer allows it to fire later than the scheduled fire date, improving the ability of the system to optimize for increased power savings and responsiveness. The timer may fire at any time between its scheduled fire date and the scheduled fire date plus the tolerance. The timer will not fire before the scheduled fire date. For repeating timers, the next fire date is calculated from the original fire date regardless of tolerance applied at individual fire times, to avoid drift. The default value is zero, which means no additional tolerance is applied. The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of this property. // As the user of the timer, you will have the best idea of what an appropriate tolerance for a timer may be. A general rule of thumb, though, is to set the tolerance to at least 10% of the interval, for a repeating timer. Even a small amount of tolerance will have a significant positive impact on the power usage of your application. The system may put a maximum value of the tolerance. // 这是7.0以后新增的一个属性,由于NSTimer并不彻底精准,经过这个值设置偏差范围 @property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0); // 中止 Timer ---> 惟一的方法将定时器从循环池中移除 - (void)invalidate; // 获取定时器是否有效 @property (readonly, getter=isValid) BOOL valid; // 获取参数信息---> 一般传入的是 nil @property (nullable, readonly, retain) id userInfo; @end NS_ASSUME_NONNULL_END
一、参数repeats是指定是否循环执行,YES将循环,NO将只执行一次。 二、timerWithTimeInterval 这两个类方法建立出来的对象若是不用 addTimer: forMode方法手动加入主循环池中,将不会循环执行。 三、scheduledTimerWithTimeInterval 这两个方法会将定时器添加到当前的运行循环,运行循环的模式为默认模式。 四、init方法须要手动加入循环池,它会在设定的启动时间启动。
NSTimer 使用过程当中的问题:
一、 内存释放问题
若是咱们启动了一个定时器,在某个界面释放前,将这个定时器中止,甚至置为nil,都不能使这个界面释放,缘由是系统的循环池中还保有这个对象。
( timer都会对它的target进行retain,咱们须要当心对待这个target的生命周期问题,尤为是重复性的timer)多线程
因此咱们须要这样作:app
-(void)dealloc{ NSLog(@"dealloc:%@",[self class]); } - (void)viewDidLoad { [super viewDidLoad]; timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES]; UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 100)]; btn.backgroundColor=[UIColor redColor]; [btn addTarget:self action:@selector(btn) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn]; } -(void)btn{ if (timer.isValid) { [timer invalidate]; // 从运行循环中移除, 对运行循环的引用进行一次 release timer=nil; // 将销毁定时器 } [self dismissViewControllerAnimated:YES completion:nil]; }
便利构造器,它实际上是作了两件事:
首先建立一个timer,而后将该timer添加到当前runloop的default mode中。less
也就是这个便利方法给咱们形成了只要建立了timer就能够生效的错觉,咱们固然能够本身建立timer,而后手动的把它添加到指定runloop的指定mode中去。ide
NSTimer其实也是一种资源(事件),若是看过多线程变成指引文档的话,咱们会发现全部的source(事件)若是要起做用,就得加到runloop中去。
同理timer这种资源要想起做用,那确定也须要加到runloop中才会有效喽。
若是一个runloop里面不包含任何资源(事件)的话,运行该runloop时会处于一种休眠状态等待下一个事件。oop
没有将事件添加到运行循环中测试
- (void)applicationDidBecomeActive:(UIApplication *)application { [self testTimerWithOutShedule]; } - (void)testTimerWithOutShedule { NSLog(@"Test timer without shedult to runloop"); SvTestObject *testObject3 = [[SvTestObject alloc] init]; NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject3 selector:@selector(timerAction:) userInfo:nil repeats:NO]; NSLog(@"invoke release to testObject3"); } - (void)applicationWillResignActive:(UIApplication *)application { NSLog(@"SvTimerSample Will resign Avtive!"); }
咱们新建了一个timer,为它指定了有效的target和selector,并指出了1秒后触发该消息,运行结果以下:ui
消息永远也不会触发,缘由很简单,咱们没有将timer添加到runloop中。
综上: 必须得把timer添加到runloop中,它才会生效。this
缘由主要有如下两个:
一、runloop是否运行
每个线程都有它本身的runloop,程序的主线程会自动的使runloop生效,但对于咱们本身新建的线程,它的runloop是不会本身运行起来,当咱们须要使用它的runloop时,就得本身启动。idea
- (void)applicationDidBecomeActive:(UIApplication *)application { // NSThread 建立一个子线程 [NSThread detachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:self withObject:nil]; } // 测试把timer加到不运行的runloop上的状况 - (void)testTimerSheduleToRunloop1 { NSLog(@"Test timer shedult to a non-running runloop"); SvTestObject *testObject4 = [[SvTestObject alloc] init]; NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject4 selector:@selector(timerAction:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 打开下面一行输出runloop的内容就能够看出,timer倒是已经被添加进去 //NSLog(@"the thread's runloop: %@", [NSRunLoop currentRunLoop]); // 打开下面一行, 该线程的runloop就会运行起来,timer才会起做用 //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; NSLog(@"invoke release to testObject4"); } - (void)applicationWillResignActive:(UIApplication *)application { NSLog(@"SvTimerSample Will resign Avtive!"); }
咱们新建立了一个线程,而后建立一个timer,并把它添加当该线程的runloop当中,可是运行结果以下:spa
发现这个timer知道执行退出也没有触发咱们指定的方法,若是咱们把上面测试程序中“
//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
这一行的注释去掉,则timer将会正确的掉用咱们指定的方法。
二、mode是否正确
手动添加runloop的时候,能够看到有一个参数runloopMode,这个参数是干吗的呢?
前面提到了要想timer生效,咱们就得把它添加到指定runloop的指定mode中去,一般是主线程的defalut mode。但有时咱们这样作了,却仍然发现timer仍是没有触发事件。
这是由于timer添加的时候,咱们须要指定一个mode,由于同一线程的runloop在运行的时候,任意时刻只能处于一种mode。因此只能当程序处于这种mode的时候,timer才能获得触发事件的机会。
综上: 要让timer生效,必须保证该线程的runloop已启动,并且其运行的runloopmode也要匹配。
//不重复,只调用一次。timer运行一次就会自动中止运行 myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(scrollTimer) userInfo:nil repeats:NO]; 须要重复调用, repeats参数改成 YES . ---> 定时器的模式是默认的 //每1秒运行一次function方法。 timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES]; 注意点: 将计数器的repeats设置为YES的时候,self的引用计数会加1。 所以可能会致使self(即viewController)不能release。 因此,必须在viewWillAppear的时候,将计数器timer中止,不然可能会致使内存泄露。 //取消定时器 [timer invalidate]; // 将定时器从运行循环中移除, timer = nil; // 销毁定时器 ---》 这样能够避免控制器不死 要想实现:先中止,而后再某种状况下再次开启运行timer,可使用下面的方法: 首先关闭定时器不能使用上面的方法,应该使用下面的方法: //关闭定时器 [myTimer setFireDate:[NSDate distantFuture]]; 而后就可使用下面的方法再此开启这个timer了: //开启定时器 [myTimer setFireDate:[NSDate distantPast]]; 例子:好比,在页面消失的时候关闭定时器,而后等页面再次打开的时候,又开启定时器。 (主要是为了防止它在后台运行,暂用CPU)可使用下面的代码实现: //页面将要进入前台,开启定时器 -(void)viewWillAppear:(BOOL)animated { //开启定时器 [scrollView.myTimer setFireDate:[NSDate distantPast]]; } //页面消失,进入后台不显示该页面,关闭定时器 -(void)viewDidDisappear:(BOOL)animated { //关闭定时器 [scrollView.myTimer setFireDate:[NSDate distantFuture]]; }
注意点:
[timer invalidate]是惟一的方法将定时器从循环池中移除
NSTimer能够精确到50-100毫秒.
NSTimeInterval类:是一个浮点数字,用来定义秒
NSTimer不是绝对准确的,并且中间耗时或阻塞错过下一个点,那么下一个点就pass过去了.