朋友問我一個問題,在畫面上顯示UIPickerView,另外顯示一個UILabel進行倒數計時(譬如說從12.0秒倒數到0.0秒,每0.1秒更新)。
聽起來很簡單,不就用個NSTimer就搞定了嗎?可惜不是。
當倒數計時並更新UILabel時,若手指撥動UIPickerView,那麼倒數計時會暫停、UILabel也不會更新。
真正的原因我不清楚,大概是因為UIPickerView需要追蹤使用者的手指觸控事件,所以佔據了thread的runloop,使得NSTimer無法觸發吧。
解法之一是在另一個thread使用NSTimer:
float countdown = 120;
- (void)timerFireMethod:(NSTimer*)timer
{
if(countdown > 0){
countdown--;
self.label.text = [NSString stringWithFormat:@"%2.1f", countdown/10];
NSLog(@"%2.1f", countdown/10);
}
else{
[self suspendTimer];
}
}
- (void)suspendTimer
{
if(_timer != nil)
{
[_timer invalidate];
_timer = nil;
CFRunLoopStop([_runLoop getCFRunLoop]);
_timerThread = nil;
}
}
- (void)_startTimerThread
{
@autoreleasepool {
_runLoop = [NSRunLoop currentRunLoop];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(timerFireMethod:)
userInfo:nil
repeats:YES] ;
[_runLoop run];
}
}
- (void)resumeTimer
{
if(_timer == nil)
{
_timerThread = [[NSThread alloc] initWithTarget:self
selector:@selector(_startTimerThread)
object:nil];
[_timerThread start];
}
}
其中resumeTimer、suspendTimer是使用介面,把你想要執行的程式寫在timerFireMethod:裡。
解法之二是使用GCD(Grand Central Dispatch):
float countdown;
-(void)tick
{
self.label.text = [NSString stringWithFormat:@"%2.1f", countdown/10];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
countdown = 120;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
ds_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(ds_timer, dispatch_time(DISPATCH_TIME_NOW, 0), 0.1*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(ds_timer, ^(){
if(countdown > 0){
NSLog(@"tick %2.1f", countdown/10);
countdown--;
[self performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:YES];
}
else{
//over
}
});
dispatch_resume(ds_timer);
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
dispatch_suspend(ds_timer);
dispatch_release(ds_timer);
dispatch_release(queue);
}
利用timer dispatch source便能每隔一段時間產生出某事件,然後指定想要執行的程式碼,要注意的是,想要更新iOS的畫面時,必須在main thread裡進行,所以上面程式碼使用了 [self performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:YES];這個方法。
應該也能以NSOperation與NSOperationQueue解決,但我還沒試。
參考文章:
- John Boydon的How to execute a NSTimer from NSThread 。
- Apple文件Threading Programming Guide、Timer Programming Topics、Concurrency Programming Guide、Grand Central Dispatch (GCD) Reference。
No comments:
Post a Comment