iOS 學習日誌 (2) --- NSTimer[轉載]

1. NSRunLoopCommonModes和Timer
    當使用NSTimer的scheduledTimerWithTimeInterval方法時。事實上此時Timer會被加入到當前線程的Run Loop中,且模式是默認的NSDefaultRunLoopMode。而若是當前線程就是主線程,也就是UI線程時,某些UI事件,好比UIScrollView的拖動操做,會將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個過程當中,默認的NSDefaultRunLoopMode模式中註冊的事件是不會被執行的。也就是說,此時使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不會執行。
    因此爲了設置一個不被UI干擾的Timer,咱們須要手動建立一個Timer,而後使用NSRunLoop的addTimer:forMode:方法來把Timer按照指定模式加入到Run Loop中。這裏使用的模式是:NSRunLoopCommonModes,這個模式等效於NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結合。(參考Apple文檔)
參考代碼:
- (void)viewDidLoad
{
    [super viewDidLoad];
  NSLog(@"主線程 %@", [NSThread currentThread]);
    //建立Timer
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
   注意:將計數器的repeats設置爲YES的時候,self的引用計數會加1。所以可能會致使self(即viewController)不能release
    //使用NSRunLoopCommonModes模式,把timer加入到當前Run Loop中。
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
//timer的回調方法
- (void)timer_callback
{
    NSLog(@"Timer %@", [NSThread currentThread]);
}
輸出:
主線程 <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
2. NSThread和Timer
    上面講的NSRunLoopCommonModes和Timer中有一個問題,這個Timer本質上是在當前線程的Run Loop中循環執行的,所以Timer的回調方法不是在另外一個線程的。那麼怎樣在真正的多線程環境下運行一個Timer呢?
    能夠先試試NSThread。同上,咱們仍是會把Timer加到Run Loop中,只不過這個是在另外一個線程中,所以咱們須要手動執行Run Loop(經過NSRunLoop的run方法),同時注意在新的線程執行中加入@autoreleasepool。
完整代碼以下:
- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"主線程 %@", [NSThread currentThread]);
    //建立並執行新的線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
    [thread start];
}
- (void)newThread
{
    @autoreleasepool
    {
        //在當前Run Loop中添加timer,模式是默認的NSDefaultRunLoopMode
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
        //開始執行新線程的Run Loop
        [[NSRunLoop currentRunLoop] run];
    }
}
//timer的回調方法
- (void)timer_callback
{
    NSLog(@"Timer %@", [NSThread currentThread]);
}
 輸出:
主線程 <NSThread: 0x7118800>{name = (null), num = 1}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}
3. GCD中的Timer
GCD中的Timer應該是最靈活的,並且是多線程的。GCD中的Timer是靠Dispatch Source來實現的。
所以先須要聲明一個dispatch_source_t本地變量:
@interface ViewController ()
{
    dispatch_source_t _timer;
}
接着經過dispatch_source_create函數來建立一個專門的Dispatch Source,接着經過dispatch_source_set_timer函數來設置Timer的參數,注意這裏的時間參數有些蛋疼。
開始時間的類型是dispatch_time_t,最好用dispatch_time或者dispatch_walltime函數來建立dispatch_time_t對象。若是須要Timer當即執行,能夠傳入dispatch_time(DISPATCH_TIME_NOW, 0)。
internal和leeway參數分別表示Timer的間隔時間和精度。類型都是uint64_t。間隔時間的單位居然是納秒。能夠藉助預約義的NSEC_PER_SEC宏,好比若是間隔時間是兩秒的話,那interval參數就是:2 * NSEC_PER_SEC。
leeway就是精度參數,表明系統能夠延時的時間間隔,最高精度固然就傳0。
而後經過dispatch_source_set_event_handler函數來設置Dispatch Source的事件回調,這裏固然是使用Block了。
最後全部dispatch_source_t建立後默認都是暫停狀態的,因此必須經過dispatch_resume函數來開始事件監聽。這裏就表明着開始Timer。 
完整代碼:
NSLog(@"主線程 %@", [NSThread currentThread]);
//間隔仍是2秒
uint64_t interval = 2 * NSEC_PER_SEC;
//建立一個專門執行timer回調的GCD隊列
dispatch_queue_t queue = dispatch_queue_create("my queue", 0);
//建立Timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函數設置timer參數
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//設置回調
dispatch_source_set_event_handler(_timer, ^()
{
    NSLog(@"Timer %@", [NSThread currentThread]);
});
//dispatch_source默認是Suspended狀態,經過dispatch_resume函數開始它
dispatch_resume(_timer);
輸出:
主線程 <NSThread: 0x711fab0>{name = (null), num = 1}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
相關文章
相關標籤/搜索