本文約100行代碼,讀完大概用時(shí)5-10分鐘,理解的話看個(gè)人知識(shí)掌握程度。
App在開發(fā)的過程中,經(jīng)常會(huì)遇到倒計(jì)時(shí)等等與時(shí)間計(jì)算有關(guān)的需求,這時(shí)就需要我們?nèi)ナ褂枚〞r(shí)器了,本篇我們就來盤點(diǎn)盤點(diǎn)iOS中的三大定時(shí)器:NSTimer、dispatch_source_t和CADisplayLink。
一、NSTimer
1.NSTimer的介紹
NSTimer應(yīng)該是新手最耳熟能詳?shù)亩〞r(shí)器了,通過Apple開發(fā)文檔的描述 A timer that fires after a certain time interval has elapsed, sending a specified message to a target object. 我們可以看到它是通過間隔一定的時(shí)間,向目標(biāo)對(duì)象發(fā)送指定的消息(OC中調(diào)用方法在底層就是發(fā)送消息)來實(shí)現(xiàn)定時(shí)器的功能的。NSTimer在使用的過程中其實(shí)是有很多小細(xì)節(jié)需要注意的,下面都會(huì)講到。
2.NSTimer的方法
NSTimer有3個(gè)timerWith類方法(初始化):
/// @param ti 定時(shí)器的時(shí)間間隔
/// @param invocation 方法調(diào)用
/// @param yesOrNo 是否重復(fù)執(zhí)行
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
/// @param ti 定時(shí)器的時(shí)間間隔
/// @param aTarget 目標(biāo)對(duì)象(一般是self)
/// @param aSelector 方法調(diào)用
/// @param yesOrNo 是否重復(fù)執(zhí)行
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
/// @param interval 定時(shí)器的時(shí)間間隔
/// @param repeats 是否重復(fù)執(zhí)行
/// @param block 方法調(diào)用(代碼塊的形式)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
這3種類方法需要你手動(dòng)將timer對(duì)象添加到runloop中;
// 在主runloop上添加 [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
和3個(gè)scheduledTimer類方法(初始化):
/// @param ti 定時(shí)器的時(shí)間間隔
/// @param invocation 方法調(diào)用
/// @param yesOrNo 是否重復(fù)執(zhí)行
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
/// @param ti 定時(shí)器的時(shí)間間隔
/// @param aTarget 目標(biāo)對(duì)象(一般是self)
/// @param aSelector 方法調(diào)用
/// @param yesOrNo 是否重復(fù)執(zhí)行
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
/// @param interval 定時(shí)器的時(shí)間間隔
/// @param repeats 是否重復(fù)執(zhí)行
/// @param block 方法調(diào)用(代碼塊的形式)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
這3種類方法需要會(huì)自動(dòng)將timer對(duì)象添加到當(dāng)前runloop(默認(rèn)是主runloop)中,并且mode為NSDefaultRunLoopMode;
以及2個(gè)initWith實(shí)例方法(初始化):
/// 需要手動(dòng)將timer對(duì)象添加到runloop中
/// @param date 開始執(zhí)行的日期
/// @param interval 定時(shí)器的時(shí)間間隔
/// @param repeats 是否重復(fù)執(zhí)行
/// @param block 方法調(diào)用(代碼塊的形式)
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
/// 需要手動(dòng)將timer對(duì)象添加到runloop中
/// @param date 開始執(zhí)行的日期
/// @param ti 定時(shí)器的時(shí)間間隔
/// @param t 目標(biāo)對(duì)象(一般是self)
/// @param s 方法調(diào)用
/// @param ui 可自定義的參數(shù)
/// @param rep 是否重復(fù)執(zhí)行
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep;
以上8個(gè)初始化方法,scheduledTimer方法會(huì)自動(dòng)添加timer到當(dāng)前runloop,其他則不會(huì)

使用timerWith和initWith 時(shí)需要手動(dòng)添加timer到runloop

2個(gè)常用的實(shí)例方法:
// 開始執(zhí)行
- (void)fire;
// 銷毀
- (void)invalidate;
fire會(huì)立即調(diào)用方法,在執(zhí)行完后,如果不是重復(fù)的timer,會(huì)立即 invalidate ;
invalidate會(huì)停止重復(fù)的timer(不重復(fù)的執(zhí)行一次后會(huì)自動(dòng)invalidate),并將其從runloop中remove。

3.NSTimer的使用示例
3.1 在一個(gè)普通的VC中使用:
@property (nonatomic, strong) NSTimer *timer; //作為屬性一般使用strong修飾,因?yàn)閠imer是一個(gè)對(duì)象,需要被持有者強(qiáng)引用以防提前釋放
// 這里使用weakSelf來避免循環(huán)引用從而導(dǎo)致內(nèi)存泄漏
__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf changeLabelText];
}];
值得注意的是,當(dāng)我們使用 target: selector: 的方式時(shí),target后面使用weakSelf 并不能避免循環(huán)引用,此時(shí)timer依然會(huì)對(duì)self進(jìn)行強(qiáng)引用,會(huì)導(dǎo)致內(nèi)存泄漏,下面的代碼是錯(cuò)誤的:
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(changeLabelText) userInfo:nil repeats:YES];
如果此時(shí)頁(yè)面上有scrollView或者tableView等在滑動(dòng)時(shí),需要手動(dòng)更改timer的mode:
/* 當(dāng)scrollView滾動(dòng)的時(shí)候,當(dāng)前的 MainRunLoop 會(huì)處于 UITrackingRunLoopMode 的模式下,
在這個(gè)模式下,是不會(huì)處理 NSDefaultRunLoopMode 的任務(wù)的 */
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
3.2 立即執(zhí)行timer的方法調(diào)用:
// 執(zhí)行fire方法后,會(huì)立即執(zhí)行本來需要時(shí)間間隔后才執(zhí)行的指定方法調(diào)用
// 對(duì)于重復(fù)的timer,它是一次額外的操作,并且不會(huì)打破正常的schedule
// 對(duì)于不重復(fù)的timer,它一觸發(fā)完后,timer就被invalidate,就不管原來設(shè)定的時(shí)間間隔了
[_timer fire];
3.3 timer的釋放與銷毀:
if ([_timer isValid]) {
[_timer invalidate]; //對(duì)于不重復(fù)的timer可以不寫,因?yàn)椴恢貜?fù)的timer在執(zhí)行完后就自動(dòng)invalidate了
_timer = nil;
}
PS:看到很多文章說timer的銷毀不能放在dealloc中,要放在- (void)viewDidDisappear:(BOOL)animated中,因?yàn)樵赿ealloc中并不會(huì)去執(zhí)行。這種說法一般是不對(duì)的,首先,dealloc中不會(huì)去執(zhí)行大概率是出現(xiàn)了循環(huán)引用,此時(shí)VC仍然被timer強(qiáng)引用,導(dǎo)致VC沒法dealloc,那么timer當(dāng)然不會(huì)去執(zhí)行銷毀;其次,viewDidDisappear時(shí)去銷毀那么你在跳往下一級(jí)頁(yè)面而不是返回上一級(jí)頁(yè)面的時(shí)候,此時(shí)當(dāng)前頁(yè)面一般是要繼續(xù)存在的,這么做就將當(dāng)前頁(yè)面的timer銷毀了,肯定是不對(duì)的,正確的做法是使用上面timer的block API + weakSelf來避免循環(huán)使用。
4.NSTimer的更多使用技巧
4.1 暫停和啟動(dòng):
// 讓timer的fire時(shí)間為“遙遠(yuǎn)的未來”,那么它就“暫?!绷?[_timer setFireDate:[NSDate distantFuture]];
// 讓timer的fire時(shí)間為“馬上”或“遠(yuǎn)古”,那么它就啟動(dòng)了
[self.timer setFireDate:[NSDate date]];
[self.timer setFireDate:[NSDate distantPast]];
4.2 非固定時(shí)間間隔執(zhí)行timer
- (void)randomTimeTimer {
// 此處先將timer的timeInterval設(shè)置為無窮大,這樣它便不會(huì)執(zhí)行
_timer = [NSTimer timerWithTimeInterval:MAXFLOAT target:self selector:@selector(randomTimeFireMethod) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer: _timer forMode:NSDefaultRunLoopMode];
}
- (void)randomTimeFireMethod {
static int timeExecute = 0;
// 這里的4是你想要timer執(zhí)行的次數(shù)
if (timeExecute < 4) {
// 不定長(zhǎng)執(zhí)行
NSTimeInterval timeInterval = [self.randomTime[timeExecute] doubleValue];
timeExecute++;
// 使用fireDate來控制timer以達(dá)到不定長(zhǎng)執(zhí)行
_timer.fireDate = [NSDate dateWithTimeIntervalSinceNow:timeInterval];
}
}
OC中并沒有NSTimer的暫停、啟動(dòng)和非固定時(shí)間間隔的方法,我們可以使用這種奇思妙想來達(dá)到這個(gè)目的。
4.3 在子線程中使用NSTimer
5.NSTimer的注意事項(xiàng)
5.1 為什么NSTimer的銷毀需要invalidate + =nil ?
在OC中,一般我們銷毀強(qiáng)引用,會(huì)直接使用 =nil ,但是NSTimer不可以。我們來看看ARC中的NSTimer創(chuàng)建到銷毀的過程中具體的引用關(guān)系變化:
-
VC創(chuàng)建NSTimer后,此時(shí)VC對(duì)timer強(qiáng)引用,再之后timer加入到runloop后,系統(tǒng)也會(huì)強(qiáng)引用timer
NSTimer的創(chuàng)建過程 -
=nil后,VC解除了對(duì)timer的強(qiáng)引用,但此時(shí)系統(tǒng)依然對(duì)timer有強(qiáng)引用
=nil - 調(diào)用
invalidate方法后,系統(tǒng)解除對(duì)timer的強(qiáng)引用
invalidate后
綜合以上,我們需要對(duì)timerinvalidate+=nil,才能真正的銷毀NSTimer。
5.2 NSTimer不是實(shí)時(shí)的 / NSTimer可以設(shè)置Tolerance(容忍度)。
- NSTimer加入的runloop正好處在一個(gè)耗時(shí)的周期內(nèi);
- NSTimer添加的runloopMode不是當(dāng)前runloop所處的mode時(shí),如
NSDefaultRunLoopMode的NSTimer在頁(yè)面滑動(dòng)時(shí)暫停; - Tolerance大概是避免NSTimer在runloop中的不實(shí)時(shí)帶來的時(shí)間偏移的(實(shí)際開發(fā)中使用較少,暫時(shí)沒怎么研究)。
二、dispatch_source_t
1. dispatch_source_t的介紹
dispatch_source_t 是眾多DISPATCH_SOURCE種類的一種
針對(duì)NSTimer受runloop的影響而不精準(zhǔn)的問題,dispatch_source_t是一種相對(duì)精準(zhǔn)的計(jì)時(shí)器,并且它天生就可以使用GCD在子線程中執(zhí)行,解決NSTimer在主線程中導(dǎo)致卡頓的問題,但是它的缺點(diǎn)也比較明顯,就是代碼量相對(duì)比較多一點(diǎn)。
2. dispatch_source_t的使用
定義屬性:
// 此處也用strong修飾,雖然沒有 * ,但是dispatch_source_t也是對(duì)象,和普通的對(duì)象一樣,strong防止提前釋放
@property (nonatomic, strong) dispatch_source_t timer;
初始化和設(shè)定:
- (void)initTimer {
if (!_gcdTimer) {
// 創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 初始化timer(設(shè)定source_type,以及隊(duì)列)
_gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設(shè)定timer的開始時(shí)間
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC));
// 如果timer的間隔時(shí)間比較大,那么可以使用dispatch_walltime來創(chuàng)建start,可以避免誤差
dispatch_time_t start_0 = dispatch_walltime(0, 0);
// 設(shè)定timer的固定時(shí)間間隔
uint64_t interval = (uint64_t)(1 * NSEC_PER_SEC);
// 設(shè)置timer,最后一個(gè)參數(shù)為leeway,是用來設(shè)置定時(shí)器的“期望精度值”,系統(tǒng)會(huì)根據(jù)這個(gè)值延遲或提前觸發(fā)定時(shí)器
dispatch_source_set_timer(_gcdTimer, start, interval, 0);
// 設(shè)定timer的方法調(diào)用
dispatch_source_set_event_handler(_gcdTimer, ^{
// 如果timer的方法調(diào)用是UI方面相關(guān)的操作,需要在主線程中執(zhí)行(線程間通信)
dispatch_async(dispatch_get_main_queue(), ^{
[self changeLabelText];
});
});
// 開啟定時(shí)器
dispatch_resume(_gcdTimer);
}
}
dispatch_source_create方法參數(shù)詳細(xì)說明
- 第一個(gè)參數(shù):dispatch_source_type_t type為設(shè)置GCD源方法的類型,前面已經(jīng)列舉過了。
- 第二個(gè)參數(shù):uintptr_t handle Apple的API介紹說,暫時(shí)沒有使用,傳0即可。
- 第三個(gè)參數(shù):unsigned long mask Apple的API介紹說,使用DISPATCH_TIMER_STRICT,會(huì)引起電量消耗加劇,畢竟要求精確時(shí)間,所以一般傳0即可,視業(yè)務(wù)情況而定。
- 第四個(gè)參數(shù):dispatch_queue_t _Nullable queue 隊(duì)列,將定時(shí)器事件處理的Block提交到哪個(gè)隊(duì)列,可以傳Null,默認(rèn)為全局隊(duì)列。
開啟定時(shí)器:
dispatch_resume(_gcdTimer);
暫停定時(shí)器:
// 暫停
- (void)pauseTimer {
if (_gcdTimer) {
dispatch_suspend(_gcdTimer);
}
}
// 暫停后的重新開啟
dispatch_resume(_gcdTimer);
銷毀定時(shí)器:
dispatch_source_cancel(_gcdTimer);
_gcdTimer = nil;
3. dispatch_source_t的注意事項(xiàng)
timer被dispatch_suspend后是不能釋放的,否則會(huì)引起崩潰。因?yàn)镺C中并沒有dispatch_source_t的暫停和開啟狀態(tài)的記錄,所以如果我們用到了它的暫停和開啟,則我們必須手動(dòng)記錄,有dispatch_suspend則必有dispatch_resume。
4. dispatch_source_t的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 性能更好,相對(duì)更精確;
- 自帶暫停、繼續(xù);
- 天生適合在子線程中使用;
- 不需要加入到runloop中,也不需要管runloop的mode。
缺點(diǎn):
- 每次
dispatch_resume都會(huì)先執(zhí)行一次; - 本質(zhì)上也不是完全精確;
- 代碼量較多。
三、CADisplayLink
1. CADisplayLink的介紹
CADisplayLink是OC中精度最高的定時(shí)器,它是根據(jù)設(shè)備的屏幕刷新頻率來執(zhí)行操作,因此它的使用場(chǎng)景也相對(duì)當(dāng)一,比較適合用來做UI的繪制、自定義的動(dòng)畫引擎以及視頻播放的渲染。
2. CADisplayLink的方法和相關(guān)屬性
1個(gè)初始化類方法:
/// @param target 目標(biāo)對(duì)象(一般是self)
/// @param sel 方法調(diào)用
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
3個(gè)實(shí)例方法
/// 將CADisplayLink對(duì)象添加到runloop中并指定mode
/// @param runloop 加入的runloop
/// @param mode 指定runloopMode
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
/// 將CADisplayLink對(duì)象從runloop指定的mode中移除
/// @param runloop 被移除CADisplayLink對(duì)象的runloop
/// @param mode 指定的runloopMode
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
// 將CADisplayLink對(duì)象從runloop所有mode中移除
- (void)invalidate;
兩個(gè)移除方法的區(qū)別
removeFromRunLoop會(huì)將其從指定的runloop的指定mode中移除,此方法在runloop或者mode任一不匹配的情況下都無效,而且remove時(shí)需要進(jìn)行判斷,如果指定的mode中不存在,那么將會(huì)引起crash,原因是重復(fù)over-release;
invalidate是從runloop的所有模式中移除,并取消和target的關(guān)聯(lián)關(guān)系,此方法可以多次調(diào)用,不會(huì)引起crash。
3個(gè)只讀屬性:
// 當(dāng)前屏幕上顯示幀率的時(shí)間戳
@property(readonly, nonatomic) CFTimeInterval timestamp;
// 定時(shí)器的時(shí)間間隔
@property(readonly, nonatomic) CFTimeInterval duration;
// 客戶端針對(duì)其渲染的下一個(gè)時(shí)間戳
@property(readonly, nonatomic) CFTimeInterval targetTimestamp;
3個(gè)讀寫屬性:
// 是否暫停,設(shè)置了暫停后定時(shí)器將暫停,直到設(shè)置為false的時(shí)候再執(zhí)行
@property(getter=isPaused, nonatomic) BOOL paused;
// 從iOS10開始已廢棄,不要去使用
@property(nonatomic) NSInteger frameInterval;
// 每秒刷新次數(shù)(幀率)
@property(nonatomic) NSInteger preferredFramesPerSecond;
3. CADisplayLink使用示例
// 創(chuàng)建
- (void)initDisplaylink {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeLabelText)];
_displayLink.preferredFramesPerSecond = 0; //每秒刷新次數(shù),設(shè)置為0時(shí)就是默認(rèn)屏幕的最大刷新幀率
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
// 銷毀
[_displayLink invalidate];
_displayLink = nil;
4. CADisplayLink的特性
CADisplayLink不能夠繼承-
修改幀率
CADisplayLink的實(shí)際幀率是由屏幕最大幀率(maximumFramesPerSecond)和參數(shù)preferredFramesPerSecond一起決定的,規(guī)則為:如果屏幕最大幀率是60,實(shí)際幀率只能是15、20、30、60中的一種;如果設(shè)置大于60的值,屏幕實(shí)際幀率為60。如果設(shè)置的是26~35之間的值,實(shí)際幀率是30;如果設(shè)置為0,會(huì)使用最高幀率。 - 在添加進(jìn)runloop時(shí)應(yīng)當(dāng)選擇高優(yōu)先級(jí)的,以保證動(dòng)畫的流暢
5. CADisplayLink防止循環(huán)引用
上面NSTimer在防止循環(huán)引用時(shí)使用了NSTimer本身提供的block方法而非傳入target的方式,但是CADisplayLink本身沒有提供block方法,只有傳入target的方式,那么我們?cè)趺幢苊庋h(huán)引用呢?
首先我們來看一種錯(cuò)誤的做法:
__weak typeof(self) weakSelf = self;
_displayLink = [CADisplayLink displayLinkWithTarget:weakSelf selector:@selector(changeLabelText:)];
_displayLink.preferredFramesPerSecond = 10;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
然后再看另一種錯(cuò)誤的做法:
// 將displayLink屬性聲明為weak
@property (nonatomic, weak) CADisplayLink *displayLink;
// 初始化
CADisplayLink *temp = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeLabelText:)];
temp.preferredFramesPerSecond = 10;
[temp addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
_displayLink = temp;
以上兩種方法都是錯(cuò)誤的,在頁(yè)面銷毀時(shí),它們無一例外的定時(shí)器都沒有被銷毀,依然在工作,原因在于此時(shí)runloop對(duì)定時(shí)器依然有強(qiáng)引用。
此時(shí)正確的做法有兩種:
1.使用NSProxy
創(chuàng)建一個(gè)繼承自NSProxy的新類GQProxy
// .h
@interface GQProxy : NSProxy
@property (weak, nonatomic) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
// .m
#import "GQProxy.h"
@implementation GQProxy
+ (instancetype)proxyWithTarget:(id)target {
GQProxy *proxy = [GQProxy alloc];
proxy.target = target;
return proxy;
}
//返回方法簽名
-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
@end
在初始化定時(shí)器時(shí)
_displayLink = [CADisplayLink displayLinkWithTarget:[GQProxy proxyWithTarget:self] selector:@selector(changeLabelText:)];
_displayLink.preferredFramesPerSecond = 10;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
這樣就可以在避免循環(huán)引用了,推薦NSTimer也使用這種方法。
2.使用category擴(kuò)展block方法
新建一個(gè)分類 CADisplayLink+GQTool
// .h
#import <QuartzCore/QuartzCore.h>
typedef void(^GQExecuteDisplayLinkBlock) (CADisplayLink *displayLink);
@interface CADisplayLink (GQTool)
@property (nonatomic,copy) GQExecuteDisplayLinkBlock executeBlock;
+ (CADisplayLink *)displayLinkWithExecuteBlock:(GQExecuteDisplayLinkBlock)block;
@end
// .m
#import "CADisplayLink+GQTool.h"
#import <objc/runtime.h>
@implementation CADisplayLink (GQTool)
- (void)setExecuteBlock:(GQExecuteDisplayLinkBlock)executeBlock{
objc_setAssociatedObject(self, @selector(executeBlock), [executeBlock copy], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (GQExecuteDisplayLinkBlock)executeBlock{
return objc_getAssociatedObject(self, @selector(executeBlock));
}
+ (CADisplayLink *)displayLinkWithExecuteBlock:(GQExecuteDisplayLinkBlock)block{
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(gq_executeDisplayLink:)];
displayLink.executeBlock = [block copy];
return displayLink;
}
+ (void)gq_executeDisplayLink:(CADisplayLink *)displayLink{
if (displayLink.executeBlock) {
displayLink.executeBlock(displayLink);
}
}
@end
在初始化定時(shí)器時(shí)
__weak typeof(self) weakSelf = self;
_displayLink = [CADisplayLink displayLinkWithExecuteBlock:^(CADisplayLink * _Nonnull displayLink) {
[weakSelf changeLabelText];
}];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
這樣也可以避免定時(shí)器對(duì)VC的強(qiáng)引用,但本質(zhì)上只是將定時(shí)器的target從控制器換成了定時(shí)器本身的類,還是存在循環(huán)引用,只不過對(duì)我們的系統(tǒng)沒有影響了。所以推薦使用NSProxy這種方法。
以上就是關(guān)于iOS中三種定時(shí)器的詳細(xì)介紹,原創(chuàng)不易,如果您覺得這篇文章對(duì)您有用的話,就順手點(diǎn)個(gè)贊+關(guān)注吧。


