天天看點

NSTimer 使用

@@@@@@@@@@@@@@@@@@@@@@@(一)

先說一下我的業務需求,最近在做一個小項目,需要用到定時器的功能,NSTimer類,期間,出現了一些小問題,不過最終通過自己的努力,終于做出來了。我想總結一下,我對NSTimer類的學習和了解。

不多說了,先上效果圖

NSTimer 使用

界面元素很簡單,兩個UIButton 開始和暫停,20表示起始倒計時。最終的效果是,按開始按鈕的時候,倒計時開始運作,按暫停按鈕的時候,計時器,停止倒計時。當倒計時為0的時候,彈出一個對話框,提示時間已到。

業務需求很簡單,但是,在我的實作中,卻出現了,一些小錯誤。 主要是暫停鍵不能點選多次,開始鍵也不能點選多次,我相信,剛開始,接觸這個NSTimer的人,也會出現這幾個問題。

直接上幾個主要的代碼:

控制器類的.h檔案中

@interface sdsViewController : UIViewController<UIAlertViewDelegate>

//定義一個定時器,做為執行個體變量

@property(nonatomic,retain) NSTimer *timer;

//顯示倒計時目前狀态

@property (retain, nonatomic) IBOutlet UILabel *timeDisplay;

//開始按鈕,響應的action

- (IBAction)startTime:(id)sender;

//暫停按鈕響應的action

- (IBAction)stopTime:(id)sender;

@end

.m中關鍵代碼

開始按鈕 響應代碼:

- (IBAction)startTime:(id)sender {

 //如果定時器對象不存在,則建立一個并啟動

    if(!_timer){

        //建立一個定時器,這個是直接加到目前消息循環中,注意與其他初始化方法的差別

       _timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES]; 

        //  [_timer fire]; //對于這個fire方法,稍後會詳解,它不是啟動一個定時器,這麼簡單

    }

}

//結束按鈕響應代碼:

- (IBAction)stopTime:(id)sender {

    if (_timer) {

        NSLog(@"調用 self.time為真!!");

            //如果定時器在運作

        if ([self.timer isValid]) {

            NSLog(@"單擊停止按鈕,取消定時器!!");

            [self.timer invalidate];

        //這行代碼很關鍵

           _timer=nil;

        }

    }

}

一切OK,現在分析程式用到的關鍵地方。

先看看NSTimer類的結構,比較簡單

Tasks

Creating a Timer

//建立一個定時器 ,以下是便利構造器方法,都懂的

+ scheduledTimerWithTimeInterval:invocation:repeats:

+ scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:

+ timerWithTimeInterval:invocation:repeats:

+ timerWithTimeInterval:target:selector:userInfo:repeats:

//初始化方法

– initWithFireDate:interval:target:selector:userInfo:repeats:

//是開始一個定時器嗎,恐怕沒那麼簡單????????

Firing a Timer

– fire

//是暫停一個定時器嗎,NO ,是Stop ,寫的很清楚

Stopping a Timer

– invalidate

//關于定時器的以下資訊

Information About a Timer

– isValid  //是否在運作

– fireDate //Returns the date at which the receiver will fire.

– setFireDate: //重新設定定時器開始運作的時間

– timeInterval  //定時器延時時間

– userInfo //其他資訊

@@@@@@@@@@@@@@@@@@@@@@@(二)

先說一下,初始化方法

+ scheduledTimerWithTimeInterval:invocation:repeats:

+ scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:

這兩個是建立一個定時器,并加入到目前運作循環中,即我們可以這樣去了解,這樣初始化一個定時器時,在(NSTimeInterval)seconds 時間之後,自動啟動定時器。

而以下兩個初始化方法這不一樣:

+ timerWithTimeInterval:invocation:repeats:

+ 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.),這就是與上面兩個方法的差別。

  //建立一個定時器

       _timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES];

_timer=[NSTimer timerWithTimeInterval:10 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES];

       //必須手動加入到目前循環中去

       NSRunLoop *runloop=[NSRunLoop currentRunLoop];

       [runloop addTimer:_timer forMode:NSDefaultRunLoopMode];

以上兩端代碼效果是一樣的

關于這個方法:

Firing a Timer

– fire

其實他并不是真的啟動一個定時器,從之前的初始化方法中我們也可以看到,建立的時候,在适當的時間,定時器就會自動啟動。那這個方法是幹什麼的呢。

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.

這是官方文檔的說法,英文說的很清楚,但我們了解還不是很到位,為了徹底搞懂它的功能。我又做了一個測試。

也是很簡單,把上面那個定時器,改變一點

//初始化的時候建立一個定時器

- (id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) {

        // Custom initialization

        //建立一個定時器,

       _timer=[NSTimer timerWithTimeInterval:10 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES];

//手動加入到循環中

      NSRunLoop *runloop=[NSRunLoop currentRunLoop];

 [runloop addTimer:_timer forMode:NSDefaultRunLoopMode];

//當然這個定時器會自動啟動,隻不多過了十秒之後,才觸發

}

return self

}

當我們單擊“開始”按鈕時,

- (IBAction)startTime:(id)sender {

    //隻是簡單地調用一下這個方法,看到底功能是什麼

  [_timer fire];

}

結果是,單擊一下按鈕,倒計時減1,單擊一下減1,即它把觸發的時間給提前了,但過十秒後倒計時還會減1,即它隻是提前觸發定時器,而不影響之前的那個定時器設定的時間,就好比我們等不及要去看一場球賽,趕緊把車開快些一樣,fire的功能就像讓我們快些到球場,但卻不影響球賽開始的時間。

還記得之前那個初始化定時器時,設定的是YES嗎,當我們,改為NO時,即不讓它循環觸發時,我們此時再單擊開始按鈕。會猛然發現,倒計時減1了,但當我們再點選開始按鈕時,會發現倒計時,不會動了。原因是:我們的定時器,被設定成隻觸發一次,再fire的時候,觸發一次,該定時器,就被自動銷毀了,以後再fire也不會觸發了。

現在 我們再看官方的解釋,或許就會更明白了,

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.

這就對了,fire并不是啟動一個定時器,隻是提前觸發而已。

@@@@@@@@@@@@@@@@@@@@@@@@(三)

上一篇的時候我們弄懂了 fire的用法,知道,它并不是啟動一個定時器

繼續讨論UITime類的其他方法:

Stopping a Timer

– invalidate

這個方法很容易讓人誤解,很容易認為他是讓定時器暫停(國文沒學好,或是英語沒學好,嘿嘿),

官方的解釋是:

This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes and releases the timer, either just before the invalidate method returns or at some later point.

If it was configured with target and user info objects, the receiver releases its references to those objects as well.

意思是說:這是唯一一個把一個定時器從一個運作循環中移除的方法。NSRunLoop object這個對象移除,并且release掉這個的定時器,或者是在這個invalidate方法傳回的之前或是在之後的某個時間段,再進行移除并release操作。

至此對于這個NSTimer類的學習,基本上,算是差不多了,一些基本功能能夠實作了。至于深入,以後有項目需要的時候,再做深入研究吧。我感覺學習就是一個不斷出錯,不斷學習,不斷總結的簡單事情,我其實把總結看的很重要,看似解決了一個問題,其實是一類,一大類問題,多花些時間也是值得地。我想以後在遇見此類問題,就比較有話語權,能夠說的滔滔不絕。也會有解決問題的積累,舉一反三。

對于這個項目,我不但學會了,弄清楚了,NSTimer這一個類的使用方法,而且讓我對官方文檔的嚴謹性,透徹性,有了進一步了解。有事沒事多看API是一個好習慣。此次也是沒有了解,英語的一些說明,才導緻了一些小錯誤,再次感歎英語真的很重要啊!!!!!!!

由這個項目我也學會了記憶體管理的一些技巧,

- (IBAction)stopTime:(id)sender {

//如果_timer不等于nil   

    if (_timer) {

        NSLog(@"調用 self.time為真!!");

// 如果定時器在運作,我們就invalidate

//        if ([self.timer isValid]) {

//            NSLog(@"單擊停止按鈕,取消定時器!!");

//

//           [self.timer invalidate];

//           同時把他置為nil

//          _timer=nil; //這就是我要說的一個對象release和對象=nil的差別,記憶體管理

//            

//        }

//不加上面這個if判斷也可以用下面這個,效果是一樣的

        [self.timer invalidate];

        _timer=nil;//這個很重要

    }

}

關于對象release和對象=nil的差別,網上也有很多讨論,我也實際驗證了一下,

個人認為  [對象 release] 後 這個對象就沒有指向任何記憶體空間了,這個時候,它是不能被使用的(可以試試  NSLog 一下  release 後的對象,有驚喜的。。。)

當 對象 = nil 時,這個對象是指向了一個記憶體空間的( 0x0000,即開始位址),這個時候這個對象是可以被NSLog出來的。。。。。

如果一個對象alloc 一次并release後,以後是不能再使用的,它雖然也指向原來的記憶體空間,但其内容是不确定的,使用的話,它的内容有時候沒事,但有時候會崩潰的。

下面這個例子可以很好地示範這個過程。

//我建立一個字元串對象

 NSString *string=[[NSString alloc] initWithFormat:@"henaho"];

        NSLog(@"對象release前的位址為:%p",string);

        NSLog(@"字元串内容為:%@",string);

//        string=nil;

     //使用之後我release

      [string release];

//       string=nil;

        if (string) {

            NSLog(@"release後對象指針不為nil");

        }else{

            NSLog(@"release後對象指針為nil"); 

        }

        NSLog(@"對象release後的内容為:%@",string);

        NSLog(@"對象release後的位址為:%p",string);

程式運作結果是: 啊 啊啊啊啊啊啊 這不是有正确結果嗎????

NSTimer 使用

我們在運作一次試試!!!!!!!崩潰啦啦啦!!!!

NSTimer 使用

但當我們在release後一個對象後,将其=nil時,下面再用到的時候就不會出錯了。

   [string release];

      string=nil;

看結果,是不是很神奇!!!!!

NSTimer 使用

綜上所述:一個好的程式設計習慣是,

[對象 release]; 

對象=nil;

這是一個好的作風,以後萬一用到的時候也不會崩掉。自己的水準目

前就這麼高,高深的也了解不了。慢慢進步……

好了,有一個定時器引發的血案終于可以結案了,開始研究NSTimer的時候感覺問題沒這麼多,但自己就是一個不斷發現問題,并解決問題的人,看我終于解決了一些關鍵的問題,感覺自己的程式設計能力有了一個很大的提高,至少解決問題的能力,有了突飛猛進。學習的信心和動力一下子,呵呵……如果有哪位看到了,感覺有疑惑,我們可以一起學習,一起進步,一起解決問題的樂趣是無窮的。哈哈,不枉費我打這麼多字和花費這麼多時間來總結,我知道,在某一天,肯定會

受益的,當然受益的不僅僅是自己……