天天看點

IOS開發定時器寫在前頭

寫在前頭

在開發過程中,總是會遇到這樣的需求:循環更新、處理,延時處理等事件。在這裡總結了一些常用的延時和按時更新的用法

dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    NSLog(@"執行任務");
});
           

注意:dispatch_after函數并不是延遲對應時間後立即執行block塊中的操作,而是将任務追加到對應隊列中,考慮到隊列阻塞等情況,是以這個任務從加入隊列到真正執行的時間是不準确的。

該方法沒有辦法取消,但是可以在執行block内代碼時進行條件判斷,然後再決定是否要執行該段代碼。

performSelector

關于performSelector的相關用法,我會單獨寫一篇文章進行總結,這裡隻介紹關于延時的兩個用法。

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
           

performSelector:withObject:afterDelay:

其實就是在内部建立了一個NSTimer,然後會添加到目前線程的Runloop中。

[self performSelector:@selector(test) withObject:nil afterDelay:4];
           

在需要取消 

test

這個方法的執行的時候調用

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(test) object:nil];
           

注意事項: 

  1. 在子線程中執行會不會調用test方法
  2. test方法執行的線程與調用時所處的線程一緻

由于該方法需要用到線程RunLoop,那麼一定要手動run,才會生效。而且,要先添加事件再調用run

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
         [self performSelector:@selector(test) withObject:nil afterDelay:2];
        [[NSRunLoop currentRunLoop] run];
});
           

NSTimer

NSTimer是我們在開發過程中很常見的一個定時器,使用方法簡單,但是會帶來很多的問題,如:不是很準确、容易造成記憶體洩漏等

常見用法:

//timerWithTimeInterval方法建立的timer,不會自動加入運作循環,需要我們手動指定模式,并手動加入運作循環。
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

//scheduledTimerWithTimeInterval方法建立完timer之後,會自動以NSDefaultRunLoopModel模式加入運作循環。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
           
- (void)startTimer {
		if (nil == self.timer) {
      
    	self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
      /*
      //也可以使用這個方法
    	self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
      */
    }
}

- (void)stopTimer {    
      if (nil == self.timer) {       
          [self.timer invalidate];  // 從運作循環中移除, 對運作循環的引用進行一次 release
          self.timer=nil;            // 将銷毀定時器
      }
}
           

關于invalid方法

我們知道NSTimer使用的時候如果不注意的話,是會造成記憶體洩漏的。原因是我們生成執行個體的時候,會對控制器retain一下。如果不對其進行管理則VC的永遠不會引用計數為零,進而造成記憶體洩漏。

NStimer還會因為加入的RunLoopMode不同,産生一些問題:

NSTimer的優勢:使用相對靈活,應用廣泛

劣勢:計時不是很準确;受runloop影響嚴重,同時易造成記憶體洩漏(調用invalid方法解決)

GCD定時器

常見用法:

//獲得隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //建立一個定時器
    self.time = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //設定開始時間
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    //設定時間間隔
    uint64_t interval = (uint64_t)(2.0* NSEC_PER_SEC);
    //設定定時器
    dispatch_source_set_timer(self.time, start, interval, 0);
    //設定回調
    dispatch_source_set_event_handler(self.time, ^{
       
      
    });
    //由于定時器預設是暫停的是以我們啟動一下
    //啟動定時器
    dispatch_resume(self.time);
    
		//暫停定時器
		dispatch_suspend(self.timer);
		//取消timer
		//一旦取消則不能再重新運作 timer,隻能重建。
		dispatch_source_cancel(timer);
           

注意事項:

1、GCD timer資源必須設定為成員變量, 否則會在建立完畢後立即釋放

2、suspend挂起或暫停後的timer要先resume才能cancel, 挂起的timer直接cancel會造成記憶體洩漏

3、dispatch_resume與dispatch_suspend/dispatch_source_cancel要成對出現,確定頁面消失前要登出timer

使用方法:

//開啟timer
- (void)startTimer {
  
  if (nil == timer) {
        //獲得隊列
    	dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    	//建立一個定時器
    	self.time = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    	//設定開始時間
    	dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
      //設定時間間隔
    	uint64_t interval = (uint64_t)(2.0* NSEC_PER_SEC);
    	//設定定時器
    	dispatch_source_set_timer(self.time, start, interval, 0);
    	//設定回調
    	dispatch_source_set_event_handler(self.time, ^{
       
      
    	});
  	}
    dispatch_resume(self.time);
}

//暫停定時器
- (void)suspendTimer {
  
  	if(nil != self.timer) {
				dispatch_suspend(self.timer);
    }
}


//取消timer
//一旦取消則不能再重新運作 timer,隻能重建。
- (void)cancelTimer {
  
  	if(nil != self.timer) {
				dispatch_source_cancel(self.timer);
      	self.timer = nil;
    }
}
           

GCDTimer的優勢:不受目前runloopMode的影響。

劣勢:雖然說不受runloopMode的影響,但是其計時效應仍不是百分之百準确的。

另外,他的觸發事件也有可能被阻塞,當GCD内部管理的所有線程都被占用時,其觸發事件将被延遲。

CADisplayLink

CADisplayLink

是用于同步螢幕重新整理頻率的計時器。當每一次螢幕重新整理的時候使用(即,預設每秒重新整理60次)

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel
  
//建立CADisplayLink對象 
/*
需要注意 定時器對象建立後 并不會馬上執行 需要添加到runloop中
*/
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
//将目前定時器對象加入一個RunLoop中
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//将目前定時器對象從一個RunLoop中移除 如果這個Runloop是定時器所注冊的最後一個  移除後定時器将被釋放
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//将定時器失效掉 調用這個函數後 會将定時器從所有注冊的Runloop中移除
- (void)invalidate;
//目前時間戳
@property(readonly, nonatomic) CFTimeInterval timestamp;
//距離上次執行所間隔的時間
@property(readonly, nonatomic) CFTimeInterval duration;
//預計下次執行的時間戳
@property(readonly, nonatomic) CFTimeInterval targetTimestamp;
//設定是否暫停
@property(getter=isPaused, nonatomic) BOOL paused;
//設定預期的每秒執行幀數 例如設定為1 則以每秒一次的速率執行
@property(nonatomic) NSInteger preferredFramesPerSecond CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);
//幀間隔,間隔多少幀執行一次。預設60幀每秒 
@property(nonatomic) NSInteger frameInterval
  CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond");
           
- (void)startDisplayLink {
  	if(nil == self.displayLink) {
    		self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(waveAnimation)];
      	self.displayLink.frameInterval = 30;//間隔30幀執行一次,即0.5秒
    		[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
}


- (void)stopDisplayLink {

    [displayLink invalidate];
    self.displayLink =nil;

}
           

優勢:依托于裝置螢幕重新整理頻率觸發事件,是以其觸發時間上是最準确的。也是

最适合做UI不斷重新整理的事件

,過渡相對流暢,無卡頓感。

缺點:

  • 由于依托于螢幕重新整理頻率,若果CPU不堪重負而影響了螢幕重新整理,那麼我們的觸發事件也會受到相應影響。
  • selector觸發的時間間隔隻能是duration的整倍數。
  • selector事件如果大于其觸發間隔就會造成掉幀現象。
  • CADisplayLink不能被繼承。

繼續閱讀