有3種方式:CADisplayLink 、NSTimer、GCDmarkdown
特色:屏幕刷新時觸發一次,會重複調用指定的方法。 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
計時器必定要加入RunLoop
中,而且選好mode才能運行。spa
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(dosomething:) userInfo:nil repeats:NO];
// TimeInterval:執行以前的等待時間
// repeats : 是否須要循環
複製代碼
上面的建立方法建立一個計時器,並自動加入到
MainRunloop
的NSDefaultRunLoopMode
中,能夠直接使用。線程
方法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;
複製代碼
不能在dealloc
中釋放定時器。由於計時器的repeats
爲YES
時,計時器會強持有self
,致使dealloc
永遠不會被調用,這個類就永遠沒法被釋放。
解決:能夠在viewDidDisappear
中釋放,這樣當類須要被回收時,就能夠正常進入dealloc
中了。
需求:先暫停,而後再某種狀況下再次開啓。主要是爲了防止它在後臺運行,暫用CPU。 好比,在頁面消失的時候關閉 ,而後等頁面再次打開 ,又開啓定時器。
//關閉定時器
[myTimer setFireDate:[NSDate distantFuture]];
//開啓定時器
[myTimer setFireDate:[NSDate distantPast]];
複製代碼
執行一次
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, 並在視圖消失前, 關閉定時器
__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];
複製代碼
.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];
複製代碼