NSTimer 的頭文件
/* 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
/** 這下面主要是一些構(gòu)造方法*/
// 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.)
// 創(chuàng)建一個(gè)定時(shí)器,但是么有添加到運(yùn)行循環(huán),我們需要在創(chuàng)建定時(shí)器后手動(dòng)的調(diào)用 NSRunLoop 對(duì)象的 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.
// 創(chuàng)建一個(gè)timer并把它指定到一個(gè)默認(rèn)的runloop模式中,并且在 TimeInterval時(shí)間后 啟動(dòng)定時(shí)器
+ (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.)
// 創(chuàng)建一個(gè)定時(shí)器,但是么有添加到運(yùn)行循環(huán),我們需要在創(chuàng)建定時(shí)器后手動(dòng)的調(diào)用 NSRunLoop 對(duì)象的 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.
// 創(chuàng)建一個(gè)timer并把它指定到一個(gè)默認(rèn)的runloop模式中,并且在 TimeInterval時(shí)間后 啟動(dòng)定時(shí)器
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
// 默認(rèn)的初始化方法,(創(chuàng)建定時(shí)器后,手動(dòng)添加到 運(yùn)行循環(huán),并且手動(dòng)觸發(fā)才會(huì)啟動(dòng)定時(shí)器)
- (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.
// 啟動(dòng) Timer 觸發(fā)Target的方法調(diào)用但是并不會(huì)改變Timer的時(shí)間設(shè)置。 即 time沒(méi)有到達(dá)到,Timer會(huì)立即啟動(dòng)調(diào)用方法且沒(méi)有改變時(shí)間設(shè)置,當(dāng)時(shí)間 time 到了的時(shí)候,Timer還是會(huì)調(diào)用方法。
- (void)fire;
// 這是設(shè)置定時(shí)器的啟動(dòng)時(shí)間,常用來(lái)管理定時(shí)器的啟動(dòng)與停止
@property (copy) NSDate *fireDate;
// 啟動(dòng)定時(shí)器
timer.fireDate = [NSDate distantPast];
//停止定時(shí)器
timer.fireDate = [NSDate distantFuture];
// 開(kāi)啟
[time setFireDate:[NSDate distanPast]]
// NSTimer 關(guān)閉
[time setFireDate:[NSDate distantFunture]]
//繼續(xù)。
[timer setFireDate:[NSDate date]];
// 這個(gè)是一個(gè)只讀屬性,獲取定時(shí)器調(diào)用間隔時(shí)間
@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之后新增的一個(gè)屬性,因?yàn)镹STimer并不完全精準(zhǔn),通過(guò)這個(gè)值設(shè)置誤差范圍
@property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0);
// 停止 Timer ---> 唯一的方法將定時(shí)器從循環(huán)池中移除
- (void)invalidate;
// 獲取定時(shí)器是否有效
@property (readonly, getter=isValid) BOOL valid;
// 獲取參數(shù)信息---> 通常傳入的是 nil
@property (nullable, readonly, retain) id userInfo;
@end
NS_ASSUME_NONNULL_END
注意:這五種初始化方法的異同:
1、參數(shù)repeats是指定是否循環(huán)執(zhí)行,YES將循環(huán),NO將只執(zhí)行一次。
2、timerWithTimeInterval 這兩個(gè)類(lèi)方法創(chuàng)建出來(lái)的對(duì)象如果不用 addTimer: forMode方法手動(dòng)加入主循環(huán)池中,將不會(huì)循環(huán)執(zhí)行。
3、scheduledTimerWithTimeInterval 這兩個(gè)方法會(huì)將定時(shí)器添加到當(dāng)前的運(yùn)行循環(huán),運(yùn)行循環(huán)的模式為默認(rèn)模式。
4、init方法需要手動(dòng)加入循環(huán)池,它會(huì)在設(shè)定的啟動(dòng)時(shí)間啟動(dòng)。
NSTimer 使用過(guò)程中的問(wèn)題:
1、 內(nèi)存釋放問(wèn)題
如果我們啟動(dòng)了一個(gè)定時(shí)器,在某個(gè)界面釋放前,將這個(gè)定時(shí)器停止,甚至置為nil,都不能使這個(gè)界面釋放,原因是系統(tǒng)的循環(huán)池中還保有這個(gè)對(duì)象。
(** timer都會(huì)對(duì)它的target進(jìn)行retain,我們需要小心對(duì)待這個(gè)target的生命周期問(wèn)題,尤其是重復(fù)性的timer**)
所以我們需要這樣做:
-(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]; // 從運(yùn)行循環(huán)中移除, 對(duì)運(yùn)行循環(huán)的引用進(jìn)行一次 release
timer=nil; // 將銷(xiāo)毀定時(shí)器
}
[self dismissViewControllerAnimated:YES completion:nil];
}
NSTimer為什么要添加到RunLoop中才會(huì)有作用
便利構(gòu)造器,它其實(shí)是做了兩件事:
首先創(chuàng)建一個(gè)timer,然后將該timer添加到當(dāng)前runloop的default mode中。
也就是這個(gè)便利方法給我們?cè)斐闪酥灰獎(jiǎng)?chuàng)建了timer就可以生效的錯(cuò)覺(jué),我們當(dāng)然可以自己創(chuàng)建timer,然后手動(dòng)的把它添加到指定runloop的指定mode中去。
NSTimer其實(shí)也是一種資源(事件),如果看過(guò)多線程變成指引文檔的話,我們會(huì)發(fā)現(xiàn)所有的source(事件)如果要起作用,就得加到runloop中去。
同理timer這種資源要想起作用,那肯定也需要加到runloop中才會(huì)有效嘍。
如果一個(gè)runloop里面不包含任何資源(事件)的話,運(yùn)行該runloop時(shí)會(huì)處于一種休眠狀態(tài)等待下一個(gè)事件。
沒(méi)有將事件添加到運(yùn)行循環(huán)中
- (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!");
}
我們新建了一個(gè)timer,為它指定了有效的target和selector,并指出了1秒后觸發(fā)該消息,運(yùn)行結(jié)果如下:

消息永遠(yuǎn)也不會(huì)觸發(fā),原因很簡(jiǎn)單,我們沒(méi)有將timer添加到runloop中。
綜上: 必須得把timer添加到runloop中,它才會(huì)生效。
NSTimer加到了RunLoop中但遲遲的不觸發(fā)事件
原因主要有以下兩個(gè):
1、runloop是否運(yùn)行
每一個(gè)線程都有它自己的runloop,程序的主線程會(huì)自動(dòng)的使runloop生效,但對(duì)于我們自己新建的線程,它的runloop是不會(huì)自己運(yùn)行起來(lái),當(dāng)我們需要使用它的runloop時(shí),就得自己?jiǎn)?dòng)。
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// NSThread 創(chuàng)建一個(gè)子線程
[NSThread detachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:self withObject:nil];
}
// 測(cè)試把timer加到不運(yùn)行的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];
// 打開(kāi)下面一行輸出runloop的內(nèi)容就可以看出,timer卻是已經(jīng)被添加進(jìn)去
//NSLog(@"the thread's runloop: %@", [NSRunLoop currentRunLoop]);
// 打開(kāi)下面一行, 該線程的runloop就會(huì)運(yùn)行起來(lái),timer才會(huì)起作用
//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(@"invoke release to testObject4");
}
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(@"SvTimerSample Will resign Avtive!");
}
我們新創(chuàng)建了一個(gè)線程,然后創(chuàng)建一個(gè)timer,并把它添加當(dāng)該線程的runloop當(dāng)中,但是運(yùn)行結(jié)果如下:

發(fā)現(xiàn)這個(gè)timer知道執(zhí)行退出也沒(méi)有觸發(fā)我們指定的方法,如果我們把上面測(cè)試程序中“
//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
這一行的注釋去掉,則timer將會(huì)正確的掉用我們指定的方法。
2、mode是否正確
手動(dòng)添加runloop的時(shí)候,可以看到有一個(gè)參數(shù)runloopMode,這個(gè)參數(shù)是干嘛的呢?
前面提到了要想timer生效,我們就得把它添加到指定runloop的指定mode中去,通常是主線程的defalut mode。但有時(shí)我們這樣做了,卻仍然發(fā)現(xiàn)timer還是沒(méi)有觸發(fā)事件。
這是因?yàn)閠imer添加的時(shí)候,我們需要指定一個(gè)mode,因?yàn)橥痪€程的runloop在運(yùn)行的時(shí)候,任意時(shí)刻只能處于一種mode。所以只能當(dāng)程序處于這種mode的時(shí)候,timer才能得到觸發(fā)事件的機(jī)會(huì)。
綜上: 要讓timer生效,必須保證該線程的runloop已啟動(dòng),而且其運(yùn)行的runloopmode也要匹配。
NSTimer 的使用
//不重復(fù),只調(diào)用一次。timer運(yùn)行一次就會(huì)自動(dòng)停止運(yùn)行
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(scrollTimer) userInfo:nil repeats:NO];
需要重復(fù)調(diào)用, repeats參數(shù)改為 YES . ---> 定時(shí)器的模式是默認(rèn)的
//每1秒運(yùn)行一次function方法。
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];
注意點(diǎn):
將計(jì)數(shù)器的repeats設(shè)置為YES的時(shí)候,self的引用計(jì)數(shù)會(huì)加1。
因此可能會(huì)導(dǎo)致self(即viewController)不能release。
所以,必須在viewWillAppear的時(shí)候,將計(jì)數(shù)器timer停止,否則可能會(huì)導(dǎo)致內(nèi)存泄露。
//取消定時(shí)器
[timer invalidate]; // 將定時(shí)器從運(yùn)行循環(huán)中移除,
timer = nil; // 銷(xiāo)毀定時(shí)器 ---》 這樣可以避免控制器不死
要想實(shí)現(xiàn):先停止,然后再某種情況下再次開(kāi)啟運(yùn)行timer,可以使用下面的方法:
首先關(guān)閉定時(shí)器不能使用上面的方法,應(yīng)該使用下面的方法:
//關(guān)閉定時(shí)器
[myTimer setFireDate:[NSDate distantFuture]];
然后就可以使用下面的方法再此開(kāi)啟這個(gè)timer了:
//開(kāi)啟定時(shí)器
[myTimer setFireDate:[NSDate distantPast]];
例子:比如,在頁(yè)面消失的時(shí)候關(guān)閉定時(shí)器,然后等頁(yè)面再次打開(kāi)的時(shí)候,又開(kāi)啟定時(shí)器。
(主要是為了防止它在后臺(tái)運(yùn)行,暫用CPU)可以使用下面的代碼實(shí)現(xiàn):
//頁(yè)面將要進(jìn)入前臺(tái),開(kāi)啟定時(shí)器
-(void)viewWillAppear:(BOOL)animated
{
//開(kāi)啟定時(shí)器
[scrollView.myTimer setFireDate:[NSDate distantPast]];
}
//頁(yè)面消失,進(jìn)入后臺(tái)不顯示該頁(yè)面,關(guān)閉定時(shí)器
-(void)viewDidDisappear:(BOOL)animated
{
//關(guān)閉定時(shí)器
[scrollView.myTimer setFireDate:[NSDate distantFuture]];
}
注意點(diǎn):
[timer invalidate]是唯一的方法將定時(shí)器從循環(huán)池中移除
NSTimer可以精確到50-100毫秒.
NSTimeInterval類(lèi):是一個(gè)浮點(diǎn)數(shù)字,用來(lái)定義秒
NSTimer不是絕對(duì)準(zhǔn)確的,而且中間耗時(shí)或阻塞錯(cuò)過(guò)下一個(gè)點(diǎn),那么下一個(gè)點(diǎn)就pass過(guò)去了.