寫在前頭
在開發過程中,總是會遇到這樣的需求:循環更新、處理,延時處理等事件。在這裡總結了一些常用的延時和按時更新的用法
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];
注意事項:
- 在子線程中執行會不會調用test方法
- 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不能被繼承。