iOS定時器和Runloop的解析以及定時器的使用

在現在很多app中,我們經常會看到輪播圖,輪播廣告等等,比如淘寶、京東商城app,他們都可以定時循環地播放廣告、圖片,背后的功臣之一就是今天的主角——定時器NSTimer

簡單地介紹了它的應用場景,接下來,說說此次要分享的技能點:

定時器的常用方式

fire方法的正確理解

NSRunloopMode對定時器的影響

子線程開啟定時器

GCD定時器

定時器引起的循環引用的解決思路

定時開始:

我創建一個HomeViewController,然后讓他成為導航控制器的

RootViewController,在HomeViewController的viewDidLoad調用定時器方法,如下代碼:

#import"HomeViewController.h"@interfaceHomeViewController()@end@implementationHomeViewController- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfregularTime];}/*

定時器的常規用法

*/- (void)regularTime{//自動開啟[NSTimerscheduledTimerWithTimeInterval:1.0f target:selfselector:@selector(timerAction) userInfo:nilrepeats:YES];}- (void)timerAction{NSLog(@"定時器:%s",__func__);}@end

運行結果:每隔一秒就打印一次。

運行結果.png

還有另外一種方式也可以開啟定時器,那就是調用這個方法:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

注意1:單獨地寫這一句代碼,默認是不會開啟定時器的,讓我們看看蘋果官方原文是怎么說的:“You must add the new timer to a run loop, using addTimer:forMode:. Then, after ti seconds have elapsed, the timer fires, sending the message aSelector to target. (If the timer is configured to repeat, there is no need to subsequently re-add the timer to the run loop.)”

也就是說,需要添加到runloop中,手動開啟。運行的結果同上。

注意2:在主線程中,runloop是默認開啟,如果是在子線程中開啟開啟定時器,那么我們還需要手動開啟runloop。運行的結果同上。

注意3:NSRunLoopCommonModesNSDefaultRunLoopMode優先級使用場景不同:一般都是默認模式。

當使用NSTimer的scheduledTimerWithTimeInterval方法時,此時Timer會被加入到當前線程的RunLoop中,且模式是默認的NSDefaultRunLoopMode,如果當前線程就是主線程,也就是UI線程時,某些UI事件,比如UIScrollView的拖動操作,會將RunLoop切換成NSEventTrackingRunLoopMode模式,在這個過程中,默認的NSDefaultRunLoopMode模式中注冊的事件是不會被執行的,也就是說,此時使用scheduledTimerWithTimeInterval添加到RunLoop中的Timer就不會執行。

所以為了設置一個不被UI干擾的Timer,使用的模式是:NSRunLoopCommonModes,這個模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結合。(參考官方文檔)

代碼如下所示:

- (void)viewDidLoad {? ? [superviewDidLoad];//在主線程中開啟定時器[selfregularTime];//在子線程中開啟定時器//? ? [NSThread detachNewThreadWithBlock:^{//? ? NSLog(@"%@",[NSThread currentThread]);//? ? [self regularTime];//? ? }];}- (void)regularTime{? ? timer = [NSTimertimerWithTimeInterval:1.0f target:selfselector:@selector(timerAction) userInfo:nilrepeats:YES];//runloop中添加定時器[[NSRunLoopcurrentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];//在子線程中啟用定時器必須開啟runloop//? ? [[NSRunLoop currentRunLoop] run];}- (void)timerAction{NSLog(@"定時器:%s",__func__);}

簡單說說fire方法,fire是火焰的意思,從字面意思可以聯想到燃料、加速的意思,that’s right![timer fire]——>就是加速計時的意思,我們通過比如點擊事件,來讓定時器人為地加速計時,這個比較簡單,這里就不多贅述。

GCD定時器,通過創建隊列、創建資源來實現定時的功能,如下代碼所示:

注意:如果延遲2秒才開啟定時器,那么dispatch_resume(gcdTimer)必須寫在外面。

#import"HomeViewController.h"@interfaceHomeViewController(){NSTimer* timer;}@end@implementationHomeViewController- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfgcdTimer:1repeats:YES];}- (void)gcdTimer:(int)timerInterVal repeats:(BOOL)repeat{//創建隊列dispatch_queue_tqueue = dispatch_queue_create("my queue",0);//創建資源dispatch_source_t gcdTimer = dispatch_source_create(&_dispatch_source_type_timer,0,0, queue);? ? dispatch_source_set_timer(gcdTimer,dispatch_time(DISPATCH_TIME_NOW,0),1*NSEC_PER_SEC,0);? ? dispatch_source_set_event_handler(gcdTimer, ^{if(repeat) {NSLog(@"重復了");? ? ? ? ? ? [selftimerAction];? ? ? ? }else{//? ? ? ? ? ? [self timerAction];//? ? ? ? ? ? //調用這個方法,釋放定時器//? ? ? ? ? ? dispatch_source_cancel(gcdTimer);//延遲兩秒會出現什么情況呢?/*

為何會調用兩次?2秒之后再觸發定時器后,耽擱了0.001秒去cancel,那定時器已經再次

觸發了,所以走了兩次,解決的方法就是把cancel寫在外面。

*/dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)? ? ? ? ? ? (timerInterVal*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{? ? ? ? ? ? ? ? [selftimerAction];? ? ? ? ? ? });? ? ? ? ? ? dispatch_source_cancel(gcdTimer);? ? ? ? }? ? });? ? dispatch_resume(gcdTimer);}/*

定時器的常規用法

*/- (void)regularTime{//自動開啟[NSTimerscheduledTimerWithTimeInterval:1.0f target:selfselector:@selector(timerAction) userInfo:nilrepeats:YES];}- (void)timerAction{NSLog(@"定時器:%s",__func__);}

定時器循環引用的解決思路

循環引用出現的場景:

eg:有兩個控制器A和B,A 跳轉到B中,B開啟定時器,但是當我返回A界面時,定時器依然還在走,控制器也并沒有執行dealloc方法銷毀掉。

為何會出現循環引用的情況呢?

原因是:定時器對控制器 (self) 進行了強引用。

先說簡單的解決思路:

蘋果官方為了給我們解決這個循環引用的問題,提供了一個定時器的新的自帶方法:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

代碼如下:

- (void)regularTime{//用蘋果自帶的方法,使用weakself就可以解決定時器循環引用問題__weaktypeof(self)weakSelf =self;? ? timer =? [NSTimerscheduledTimerWithTimeInterval:1.0f repeats:YESblock:^(NSTimer* _Nonnull timer) {? ? ? ? [weakSelf timerAction];? ? }];}

第二種思路:

既然引發循環引用是因為Timer對self的強引用,那我讓Timer不對self強引用,不就解決了。

本人非常興奮地實驗了兩種方法:timer=nil(失敗)、__weak typeof(self)weakself = self(失敗),都是調用系統自帶的方法,如下代碼:

- (void)regularTime{//自動開啟//timer置為nil或者__weak typeof(self)weakself = self也無法解決定時器循環引用問題__weaktypeof(self)weakself =self;? ? timer = [NSTimerscheduledTimerWithTimeInterval:1.0f target:weakself selector:@selector(timerAction) userInfo:nilrepeats:YES];}

既然如此,那該如何是好?

答案是:通過類擴展,自己改寫NSTimer的類方法,在控制器中調用新的類方法,直接show the code:

NSTimer+HomeTimer.h中:

#import@interfaceNSTimer(HomeTimer)+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)timerInterval block:(void(^)())block repeats:(BOOL)repeat;+ (void)timerAction:(NSTimer*)timer;@end

NSTimer+HomeTimer.m中:

#import"NSTimer+HomeTimer.h"@implementationNSTimer(HomeTimer)+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)timerInterval block:(void(^)())block repeats:(BOOL)repeat{return[selftimerWithTimeInterval:timerInterval target:selfselector:@selector(timerAction:) userInfo:block repeats:YES];}+ (void)timerAction:(NSTimer*)timer{void(^block)() = [timer userInfo];if(block) {? ? ? ? block();? ? }}@end

類擴展寫好之后,在控制器中調用,重寫類方法,讓定時器對NSTimer類強引用,類是沒有內存空間的,就沒有循環引用,跟蘋果提供的新方法是類似的處理方式,如下代碼和運行結果所示:

#import"HomeTimerViewController.h"#import"NSTimer+HomeTimer.h"@interfaceHomeTimerViewController(){NSTimer* timer;}@end@implementationHomeTimerViewController- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfregularTime];self.view.backgroundColor = [UIColorgreenColor];}- (void)regularTime{? ? __weaktypeof(self)weakSelf =self;? ? timer = [NSTimertimerWithTimeInterval:1.0f block:^{? ? ? ? [weakSelf timerAction];? ? } repeats:YES];? ? [[NSRunLoopcurrentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];}- (void)timerAction{NSLog(@"定時器:%s",__func__);}- (void)dealloc{NSLog(@"%s",__func__);}@end

運行結果.png

定時結束:用時2小時32分鐘

總結:

我之前在開發app的時候,對定時器更多是會用的層次,經過這次的深入學習,對定時器的原理有了更深入的理解、認識,技術的提升,很多時候都是基礎知識的延伸,對原理理解透徹,很多東西就可以舉一反三,全部都通了,希望對自己和各位同道人有所幫助。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容