- NSRunLoopCommonModes和Timer
- NSThread和Timer
- GCD中的Timer
第一:NSRunLoopCommonModes和Timer
當使用NSTimer的scheduledTimerWithTimeInterval方法時。事實上此時Timer會被加入到當前線程的Run Loop中,且模式是默認的NSDefaultRunLoopMode。而如果當前線程就是主線程,也就是UI線程時,某些UI事件,比如UIScrollView的拖動操作,會將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個過程中,默認的NSDefaultRunLoopMode模式中注冊的事件是不會被執(zhí)行的。也就是說,此時使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不會執(zhí)行。
所以為了設置一個不被UI干擾的Timer,我們需要手動創(chuàng)建一個Timer,然后使用NSRunLoop的addTimer:forMode:方法來把Timer按照指定模式加入到Run Loop中。這里使用的模式是:NSRunLoopCommonModes,這個模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結(jié)合。
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"主線程 %@", [NSThread currentThread]);
//創(chuàng)建Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
//使用NSRunLoopCommonModes模式,把timer加入到當前Run Loop中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
//timer的回調(diào)方法
- (void)timer_callback
{
NSLog(@"Timer %@", [NSThread currentThread]);
}
第二:NSThread和Timer
上面講的NSRunLoopCommonModes和Timer中有一個問題,這個Timer本質(zhì)上是在當前線程的Run Loop中循環(huán)執(zhí)行的,因此Timer的回調(diào)方法不是在另一個線程的。那么怎樣在真正的多線程環(huán)境下運行一個Timer呢?
可以先試試NSThread。同上,我們還是會把Timer加到Run Loop中,只不過這個是在另一個線程中,因此我們需要手動執(zhí)行Run Loop(通過NSRunLoop的run方法),同時注意在新的線程執(zhí)行中加入@autoreleasepool。
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"主線程 %@", [NSThread currentThread]);
//創(chuàng)建并執(zhí)行新的線程
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];
//開始執(zhí)行新線程的Run Loop
[[NSRunLoop currentRunLoop] run];
}
}
//timer的回調(diào)方法
- (void)timer_callback
{
NSLog(@"Timer %@", [NSThread currentThread]);
}
第三:GCD中的Timer
GCD中的Timer應該是最靈活的,而且是多線程的。GCD中的Timer是靠Dispatch Source來實現(xiàn)的。
因此先需要聲明一個dispatch_source_t本地變量:
@interface ViewController ()
{
dispatch_source_t _timer;
}
接著通過dispatch_source_create函數(shù)來創(chuàng)建一個專門的Dispatch Source,接著通過dispatch_source_set_timer函數(shù)來設置Timer的參數(shù),注意這里的時間參數(shù)有些蛋疼。
開始時間的類型是dispatch_time_t,最好用dispatch_time或者dispatch_walltime函數(shù)來創(chuàng)建dispatch_time_t對象。如果需要Timer立即執(zhí)行,可以傳入dispatch_time(DISPATCH_TIME_NOW, 0)。
internal和leeway參數(shù)分別表示Timer的間隔時間和精度。類型都是uint64_t。間隔時間的單位竟然是納秒。可以借助預定義的NSEC_PER_SEC宏,比如如果間隔時間是兩秒的話,那interval參數(shù)就是:2 * NSEC_PER_SEC。
leeway就是精度參數(shù),代表系統(tǒng)可以延時的時間間隔,最高精度當然就傳0。
然后通過dispatch_source_set_event_handler函數(shù)來設置Dispatch Source的事件回調(diào),這里當然是使用Block了。
最后所有dispatch_source_t創(chuàng)建后默認都是暫停狀態(tài)的,所以必須通過dispatch_resume函數(shù)來開始事件監(jiān)聽。這里就代表著開始Timer。
NSLog(@"主線程 %@", [NSThread currentThread]);
//間隔還是2秒
uint64_t interval = 2 * NSEC_PER_SEC;
//創(chuàng)建一個專門執(zhí)行timer回調(diào)的GCD隊列
dispatch_queue_t queue = dispatch_queue_create("my queue", 0);
//創(chuàng)建Timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函數(shù)設置timer參數(shù)
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//設置回調(diào)
dispatch_source_set_event_handler(_timer, ^()
{
NSLog(@"Timer %@", [NSThread currentThread]);
});
//dispatch_source默認是Suspended狀態(tài),通過dispatch_resume函數(shù)開始它
dispatch_resume(_timer);