NSTimer的使用[zhuang]

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 /** 這下面主要是一些構造方法*/ // 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]; }

NSTimer爲何要添加到RunLoop中才會有做用

便利構造器,它實際上是作了兩件事:
首先建立一個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


Snip20151212_4.png

消息永遠也不會觸發,緣由很簡單,咱們沒有將timer添加到runloop中。
  綜上: 必須得把timer添加到runloop中,它纔會生效。this

NSTimer加到了RunLoop中但遲遲的不觸發事件

緣由主要有如下兩個:
一、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


Snip20151212_5.png

發現這個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也要匹配。

NSTimer 的使用

//不重複,只調用一次。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過去了.



文/瀟小濺(簡書做者) 原文連接:http://www.jianshu.com/p/3ccdda0679c1 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。
相關文章
相關標籤/搜索