理解run loop后,才能徹底理解NSTimer的實現原理,也就是說NSTimer實際上依賴run loop實現的。
先看看NSTimer的兩個常用方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; //生成timer但不執行
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; //生成timer並且納入當前線程的run loop來執行
NSRunLoop與timer有關方法為:
- (void)addTimer:(NSTimer *)timer forMode:(NSString *)mode; //在run loop上注冊timer
主線程已經有run loop,所以NSTimer一般在主線程上運行都不必再調用addTimer:。但在非主線程上運行必須配置run loop,該線程的main方法示例代碼如下:
- (void)main
{
NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timer:) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:myTimer forMode:NSDefaultRunLoopMode]; //實際上這步是不需要,scheduledTimerWithTimeInterval已經納入當前線程運行。如果使用timerWithTimeInterval則需要
while (condition)
[runLoop run];
}
實際上這個線程無法退出,因為有timer事件需要處理,[runLoop run]會一直無法返回。解決辦法就是設置一個截止時間:
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //每隔10秒檢查下線程循環條件,當然時間值可以根據實際情況來定。
我們通常在主線程中使用NSTimer,有個實際遇到的問題需要注意。當滑動界面時,系統為了更好地處理UI事件和滾動顯示,主線程runloop會暫時停止處理一些其它事件,這時主線程中運行的NSTimer就會被暫停。解決辦法就是改變NSTimer運行的mode(mode可以看成事件類型),不使用缺省的NSDefaultRunLoopMode,而是改用NSRunLoopCommonModes,這樣主線程就會繼續處理NSTimer事件了。具體代碼如下:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
大家可以參看博文http://bluevt.org/?p=209,加深理解NSTimer和NSRunLoop的關系。
以前博文中提到延遲調用的方法,其實就是在當前線程的run loop上注冊timer來實現定時運行的。所以如果是在非主線程上使用,一定要有一個run loop。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;