//創建方式1 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:NO]; [timer invalidate]; //調用創建方法後,target對象的計數器會加1,直到執行完畢,自動減1。如果是循環執行的話,就必須手動關閉,否則可以不執行釋放方法。 //推薦-->創建方式2 NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [timer invalidate];
存在延遲:不管是一次性的還是周期性的timer的實際觸發事件的時間,都會與所加入的RunLoop和RunLoop Mode有關,如果此RunLoop正在執行一個連續性的運算,timer就會被延時出發。
- (void)startDisplayLink{ self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } - (void)handleDisplayLink:(CADisplayLink *)displayLink{ //do something } - (void)stopDisplayLink{ [self.displayLink invalidate]; self.displayLink = nil; }
CADisplayLink是一個能讓我們以和屏幕刷新率同步的頻率將特定的內容畫到屏幕上的定時器類。 CADisplayLink以特定模式注冊到runloop後, 每當屏幕顯示內容刷新結束的時候,runloop就會向 CADisplayLink指定的target發送一次指定的selector消息, CADisplayLink類對應的selector就會被調用一次。
iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結束都被調用,精確度相當高。使用場合相對專一,適合做UI的不停重繪,比如自定義動畫引擎或者視頻播放的渲染。不需要在格外關心屏幕的刷新頻率了,本身就是跟屏幕刷新同步的。
1.執行一次
double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ //執行事件 });
2.重復執行
NSTimeInterval period = 1.0; //設置時間間隔 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒執行 dispatch_source_set_event_handler(_timer, ^{ //在這裡執行事件 }); dispatch_resume(_timer);
GCD的定時器和NSTimer是不一樣的,NSTimer受RunLoop影響,但是GCD的定時器不受影響,因為RunLoop也是基於GCD的
@interface ViewController () @property(nonatomic,strong)NSTimer *timer; // timer @property(nonatomic,assign)int countDown; // 倒數計時用 @property(nonatomic,strong)NSDate *beforeDate; // 上次進入後台時間 @end static int const tick = 60; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self setupNotification]; [self startCountDown]; } -(void)dealloc { [[NSNotificationCenter defaultCenter]removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter]removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; [self stopTimer]; } -(void)setupNotification { [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(enterBG) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(enterFG) name:UIApplicationWillEnterForegroundNotification object:nil]; } /** * 進入後台記錄當前時間 */ -(void)enterBG { NSLog(@"應用進入後台啦"); _beforeDate = [NSDate date]; } /** * 返回前台時更新倒計時值 */ -(void)enterFG { NSLog(@"應用將要進入到前台"); NSDate * now = [NSDate date]; int interval = (int)ceil([now timeIntervalSinceDate:_beforeDate]); int val = _countDown - interval; if(val > 1){ _countDown -= interval; }else{ _countDown = 1; } } /** * 開始倒計時 */ -(void)startCountDown { _countDown = tick; //< 重置計時 _timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; //< 需要加入手動RunLoop,需要注意的是在NSTimer工作期間self是被強引用的 [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; //< 使用NSRunLoopCommonModes才能保證RunLoop切換模式時,NSTimer能正常工作。 } -(void)timerFired:(NSTimer *)timer { if (_countDown == 0) { [self stopTimer]; NSLog(@"重新發送"); }else{ _countDown -=1; NSLog(@"倒計時中:%d",_countDown); } } - (void)stopTimer { if (_timer) { [_timer invalidate]; } }