在iOS開發(fā)中,經(jīng)常會用到定時器赤拒,iOS中常用的定時器有三種:NSTimer秫筏,GCD诱鞠,CADisplayLink。
NSTimer創(chuàng)建定時器
// 創(chuàng)建定時器 方式1
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(run) userInfo:nil repeats:YES];
// 停止定時器
[timer invalidate];
// 創(chuàng)建定時器 方式2
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 將定時器添加到runloop中这敬,否則定時器不會啟動
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 停止定時器
[timer invalidate];
方式1會自動將創(chuàng)建的定時器以默認(rèn)方式添加到當(dāng)前線程runloop中航夺,而無需手動添加。但是在此種模式下崔涂,當(dāng)滾動屏幕時runloop會進(jìn)入另外一種模式阳掐,定時器會暫停,為了解決這種問題冷蚂,可以像方式2那樣把定時器添加到NSRunLoopCommonModes模式下缭保。
方式1和方式2在設(shè)置后都會在間隔設(shè)定的時間(本例中設(shè)置為1s)后執(zhí)行test方法,如果需要立即執(zhí)行可以使用下面的代碼蝙茶。
[timer fire];
注意:NSTimer創(chuàng)建的定時器艺骂,使用時會造成循環(huán)引用(target對self做了強(qiáng)引用,self又對timer進(jìn)行了強(qiáng)引用)隆夯,從而導(dǎo)致內(nèi)存泄漏钳恕。
解決NSTimer循環(huán)應(yīng)用的方法:
1.合適的時機(jī)關(guān)閉定時器
#pragma mark - 第一種方法:合適的時機(jī)關(guān)閉定時器
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (!parent) {
[_timer invalidate];
_timer = nil;
}
}
2.更改target,并給target通過Runtime添加方法
// 添加屬性
@property (nonatomic, strong) id target;
// 第二種方法
_target = [NSObject new];
class_addMethod([_target class], @selector(run), class_getMethodImplementation([self class], @selector(run)), "v@:");
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(fire) userInfo:nil repeats:YES];
- (void)dealloc {
if (_timer) {
[_timer invalidate];
_timer = nil;
}
}
- (void)run
{
NSLog(@"Hello");
}
3.通過使用NSProxy解決循環(huán)引用
新建一個FKProxy繼承自NSProxy蹄衷,添加屬性target忧额,實現(xiàn)方法
@property (nonatomic, weak) id target;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
把timer的target改為FKProxy類型的對象
@property (nonatomic, strong) FKProxy *fkProxy;
_fkProxy = [FKProxy alloc]; //FKProxy只有alloc方法,沒有init方法
_fkProxy.target = self;
_timer3 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_fkProxy selector:@selector(run) userInfo:nil repeats:YES];
4.添加一個NSTimer的分類愧口,使用block解決循環(huán)應(yīng)用
分類實現(xiàn)方法
+ (NSTimer *)fk_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block {
void (^inBlock)(void) = [block copy];
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(fk_blockHandle:) userInfo:inBlock repeats:repeats];
}
+ (void)fk_blockHandle:(NSTimer *)timer
{
if (timer.userInfo) {
void(^block)(void) = (void (^)(void))timer.userInfo;
block();
}
}
使用分類方法創(chuàng)建定時器
__weak typeof (self) weakSelf = self;
_timer4 = [NSTimer fk_scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof (weakSelf) strongSelf = weakSelf;
[strongSelf run];
}];
GCD創(chuàng)建定時器
@property (nonatomic ,strong)dispatch_source_t timer; // 注意:此處應(yīng)該使用強(qiáng)引用 strong
// GCDTimer
- (void)gcdTimerTest {
// 1. 創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.創(chuàng)建GCD中的定時器
/*
第一個參數(shù):創(chuàng)建source的類型 DISPATCH_SOURCE_TYPE_TIMER:定時器
第二個參數(shù):0
第三個參數(shù):0
第四個參數(shù):隊列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 3.設(shè)置定時的開始時間睦番、間隔時間
/*
第一個參數(shù):定時器對象
第二個參數(shù):DISPATCH_TIME_NOW 表示從現(xiàn)在開始計時
第三個參數(shù):間隔時間 GCD里面的時間最小單位為 納秒
第四個參數(shù):精準(zhǔn)度(表示允許的誤差,0表示絕對精準(zhǔn))
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 4.設(shè)置定時器回調(diào)
dispatch_source_set_event_handler(timer, ^{
[self run];
});
// 5.啟動定時器,默認(rèn)是關(guān)閉的
dispatch_resume(timer);
self.timer = timer;
}
此處注意一定要強(qiáng)引用定時器 耍属,否則定時器執(zhí)行到 } 后將會被釋放托嚣,無定時效果。
GCD定時器時間非常精準(zhǔn)恬涧,最小的定時時間可以達(dá)到1納秒注益,所以用在非常精確的定時場合。
CADisplayLink創(chuàng)建定時器
// 創(chuàng)建displayLink
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(run:)];
// 將創(chuàng)建的displayLink添加到runloop中溯捆,否則定時器不會執(zhí)行
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 停止定時器
[displayLink invalidate];
displayLink = nil;
當(dāng)把CADisplayLink對象add到runloop中后,selector就能被周期性調(diào)用厦瓢,類似于重復(fù)的NSTimer被啟動了提揍;執(zhí)行invalidate操作時,CADisplayLink對象就會從runloop中移除煮仇,selector調(diào)用也隨即停止劳跃,類似于NSTimer的invalidate方法.
調(diào)用時機(jī):
CADisplayLink是一個和屏幕刷新率同步的定時器類。CADisplayLink以特定模式注冊到runloop后浙垫,每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時候刨仑,runloop就會向CADisplayLink指定的target發(fā)送一次指定的selector消息郑诺,CADisplayLink類對應(yīng)的selector就會被調(diào)用一次,所以可以使用CADisplayLink做一些和屏幕操作相關(guān)的操作杉武。
重要屬性:
frameInterval
NSInteger類型的值辙诞,用來設(shè)置間隔多少幀調(diào)用一次selector方法,默認(rèn)值是1轻抱,即每幀都調(diào)用一次飞涂。duration
readOnly的CFTimeInterval值,表示兩次屏幕刷新之間的時間間隔祈搜。需要注意的是较店,該屬性在target的selector被首次調(diào)用以后才會被賦值。selector的調(diào)用間隔時間計算方式是:調(diào)用間隔時間 = duration × frameInterval容燕。
比較NSTimer梁呈,GCD,CADisplayLink
NSTimer與GCD相比較,NSTimer計時不準(zhǔn)確蘸秘,原因:
- RunLoop循環(huán)處理的時間
- 受RunLoop模式的影響
NSTimer與GCD是不同的
- 都是源捧杉,一個是RunLoop的源,一個Dispatch的源
- GCD timer不需要加入mode
總結(jié)
- GCD timer精度高
- GCD timer主線程執(zhí)行會RunLoop影響秘血,子線程不受影響
- GCD timer不受模式切換的影響
下面結(jié)合NSTimer來介紹 CADisplayLink味抖,與NSTimer不同的地方有:
1、原理不同
CADisplayLink是一個能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫到屏幕上的定時器類灰粮。 CADisplayLink以特定模式注冊到runloop后仔涩, 每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時候,runloop就會向 CADisplayLink指定的target發(fā)送一次指定的selector消息粘舟, CADisplayLink類對應(yīng)的selector就會被調(diào)用一次熔脂。 NSTimer以指定的模式注冊到runloop后,每當(dāng)設(shè)定的周期時間到達(dá)后柑肴,runloop會向指定的target發(fā)送一次指定的selector消息霞揉。2、周期設(shè)置方式不同
iOS設(shè)備的屏幕刷新頻率(FPS)是60Hz晰骑,因此CADisplayLink的selector 默認(rèn)調(diào)用周期是每秒60次适秩,這個周期可以通過frameInterval屬性設(shè)置, CADisplayLink的selector每秒調(diào)用次數(shù)=60/ frameInterval硕舆。比如當(dāng) frameInterval設(shè)為2秽荞,每秒調(diào)用就變成30次。因此抚官, CADisplayLink 周期的設(shè)置方式略顯不便扬跋。
NSTimer的selector調(diào)用周期可以在初始化時直接設(shè)定,相對就靈活的多凌节。3钦听、精確度不同
iOS設(shè)備的屏幕刷新頻率是固定的洒试,CADisplayLink在正常情況下會在每次刷新結(jié)束都被調(diào)用,精確度相當(dāng)高朴上。
NSTimer的精確度就顯得低了點垒棋,比如NSTimer的觸發(fā)時間到的時候,runloop如果在忙于別的調(diào)用余指,觸發(fā)時間就會推遲到下一個runloop周期捕犬。更有甚者,在OS X v10.9以后為了盡量避免在NSTimer觸發(fā)時間到了而去中斷當(dāng)前處理的任務(wù)酵镜,NSTimer新增了tolerance屬性碉碉,讓用戶可以設(shè)置可以容忍的觸發(fā)的時間范圍。4淮韭、使用場合
從原理上不難看出垢粮, CADisplayLink 使用場合相對專一, 適合做界面的不停重繪靠粪,比如視頻播放的時候需要不停地獲取下一幀用于界面渲染蜡吧。
NSTimer的使用范圍要廣泛的多,各種需要單次或者循環(huán)定時處理的任務(wù)都可以使用占键。