iOS定時器任務

有3種方式:CADisplayLink 、NSTimer、GCDmarkdown

CADisplayLink

特色:屏幕刷新時觸發一次,會重複調用指定的方法。 CADisplayLink是一個能讓咱們以和屏幕刷新率同步的頻率,將特定的內容畫到屏幕上的定時器類。一般狀況下,按照iOS設備屏幕的刷新率60次/秒。app

CADisplayLink以特定模式註冊到runloop後,每當屏幕顯示內容刷新結束時,runloop就會向CADisplayLink指定的target發送一次指定的selector消息,即調用方法。async

  • 建立
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];    
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//當把CADisplayLink對象add到runloop中後,selector就能被週期性調用
複製代碼
  • 屬性
`frameInterval`:
設置間隔多少幀`(NSInteger)`調用一次`selector`方法。默認是1,即每幀都調用一次。

`duration `:
表示兩次屏幕刷新之間的時間間隔`(CFTimeInterval)`。只讀屬性。
注意:該屬性在`target`的`selector`被首次調用之後纔會被賦值。
`selector`的調用間隔時間t 計算方式是:`t = duration × frameInterval`。
複製代碼
  • 中止及釋放
[self.displayLink invalidate];  
 self.displayLink = nil;
//  CADisplayLink對象就會從runloop中移除,selector調用也隨即中止
複製代碼
  • 優缺點

iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常狀況下會在每次刷新結束都被調用,精確度至關高。 一、但若是調用的定時方法比較耗時,超過了屏幕刷新週期,就會致使跳過若干次回調機會。 二、若是CPU過於繁忙,沒法保證屏幕60次/秒的刷新率,就會致使跳過若干次調用回調機會。跳過次數取決CPU的忙碌程度。oop

  • 應用場景

從原理上能夠看出,CADisplayLink適合作界面的不停重繪。好比,視頻播放的時候須要不停地獲取下一幀用於界面渲染。atom

NSTimer

  • 建立

計時器必定要加入RunLoop中,而且選好mode才能運行。spa

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(dosomething:) userInfo:nil repeats:NO];
// TimeInterval:執行以前的等待時間
// repeats : 是否須要循環
複製代碼

上面的建立方法建立一個計時器,並自動加入到MainRunloopNSDefaultRunLoopMode中,能夠直接使用。線程

方法scheduledTimerWithTimeInterval 在哪一個線程建立,就會被加入哪一個線程的RunLoop中就運行在哪一個線程。 本身建立的Timer,加入到哪一個線程的RunLoop中就運行在哪一個線程。代理

建立後,target對象的計數器會加1,直到執行完畢,會自動減1,自動釋放。code

  • 缺點:

一、存在延遲問題 無論是一次性的仍是週期性,timer的實際觸發時間,都會與所加入的RunLoop、RunLoop Mode有關。orm

若是所加入的RunLoop正在執行一個連續性的任務,timer就會被延時觸發。對於重複性的timer,若是延遲超過了一個週期,則會在延時結束後馬上執行,並按照以前指定的週期繼續執行。

二、頁面滑動時,定時器不工做

主線程的RunLoop裏有兩個預置的模式:

kCFRunLoopDefaultMode、UITrackingRunLoopMode都是"Common"屬性。 前者是App平時所處的狀態,後者是追蹤滑動的狀態。

當建立一個 Timer 並加到 DefaultMode 時,Timer 會獲得重複回調。但若是出現頁面的滑動,RunLoop 會將模式切換爲 TrackingRunLoopMode(爲了避免影響滑動操做)但這時 Timer 就不會被回調。

解決: 滑動和定時器調用互不影響,將這個 Timer 加入模式爲NSRunLoopCommonModes

// 建立
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
// 添加到runloop
[[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];
複製代碼

獲取runloop的方法

[NSRunLoop mainRunLoop] 這個適用於主線程中 [NSRunLoop currentRunLoop]主線程/子線程 都適用

  • 釋放

若是是定時器任務循環執行的話,就必須手動關閉釋放!

[timer invalidate];
timer = nil;
複製代碼
釋放Timer的注意點:

不能在dealloc中釋放定時器。由於計時器的repeatsYES時,計時器會強持有self,致使dealloc永遠不會被調用,這個類就永遠沒法被釋放。

解決:能夠在viewDidDisappear中釋放,這樣當類須要被回收時,就能夠正常進入dealloc中了。

暫停Timer

需求:先暫停,而後再某種狀況下再次開啓。主要是爲了防止它在後臺運行,暫用CPU。 好比,在頁面消失的時候關閉 ,而後等頁面再次打開 ,又開啓定時器。

//關閉定時器  
[myTimer setFireDate:[NSDate distantFuture]];  

//開啓定時器  
[myTimer setFireDate:[NSDate distantPast]]; 
複製代碼

GCD

執行一次

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC); 
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
    //執行事件
});
複製代碼

重複

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);  // 啓動
複製代碼

取消定時器: dispatch_cancel( _timer);

定時器的調用,放在主線程中最優! 在gcd dispatch_async中執行可能會無效!

循環引用問題

一、CADisplayLink、NSTimer會對target產生強引用,若是target又對它們強引用,就會引起循環引用。 二、runloop 會對 CADisplayLink、NSTimer產生強引用。

解決:使用weakSelf,或NSProxy(代理對象)

  • weakSelf
// 內部使用 WeakSelf, 並在視圖消失前, 關閉定時器
__weak __typeof(self) weakSelf = self;
NSTimer * timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"timer");
}];
self.timer = timer;
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼
  • NSProxy
.h
// 解決循環引用問題
@interface MyProxy : NSProxy
- (instancetype)initWithObjc:(id)objc;
+ (instancetype)proxyWithObjc:(id)objc;


.m
@interface MyProxy()
@property(nonatomic,weak) id objc;
@end

@implementation MyProxy
- (instancetype)initWithObjc:(id)objc{
    self.objc = objc;
    return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
    return [[self alloc] initWithObjc:objc];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [self.objc methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    if ([self.objc respondsToSelector:invocation.selector]) {
        [invocation invokeWithTarget:self.objc];
    }
}
複製代碼

用代理對象引用target。

NSTimer * timer = [NSTimer timerWithTimeInterval:1
                                          target:[TimerProxy proxyWithTarget:self]
                                        selector:@selector(test1)
                                        userInfo:nil
                                         repeats:YES];
self.timer = timer;
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼
相關文章
相關標籤/搜索