用GCD實現Timer

iOS延遲的操作有三種:

NSObjectperformSelector afterDelay
NSTimer
GCD 的dispatch_after
三種方法都有各自的優缺點:
NSTimerperformSelector

缺點:
  1. 必須保證一個活躍的runloop,子線程中因為不會自動開啟runloop,所以需要手動激活或把它放到MainRunLoop里。
  2. NSTimer的創建與撤銷必須在同一個線程中,performSelector 的創建和撤銷必須在同一個線程中。
  3. 內存管理有泄漏風險。

Why?

timerperformSelector 會持有target。當
當一個timerschedule時候,timer會持有targetNSRunLoop會持有timer,當invalidate被調用,NSRunLoop會釋放timer的持有,timer會釋放對targert的持有,其它沒有方法可以釋放timertarget持有。所以解決內存問題必須手動釋放timer

注:但是如果我們自己寫一個業務邏輯里面有timer,我們希望內部邏輯具有自完備性,即不需要外部手動調用invalidate,邏輯上說外部也不需要知道這個業務是否使用了timer

dispatch_after

缺點:
一旦執行,不能撤銷。而performSelector可以用
canclePreviousPerformRequestWithTarget撤銷,timer可以用invalidate撤銷

解決方案
為了解決以上的問題我們可以用GCD實現一個timer,系統會幫我們處理線程邏輯優化線程,而且不需要關心runloop問題,且調用對象不回被強行持有,只需要注意block中的循環引用就可。
具體的代碼如下:


//1.取到queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.創建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.設置timer首次執行時間,間隔,精確度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//執行timer
__weak __typeof(self)weakSelf = self;
dispatch_source_set_event_handler(timer, ^{
[weakSelf doSomeThing];
});
//4.激活timer
dispatch_resume(timer);
//5.取消timer
dispatch_source_cancel(timer);

用這個方式可以解決timer的三個缺點,但是

  1. GCD timer的API太多 。
  2. 沒有repeats,如果只想執行一次自己得寫標志位控制。
    這些我們可以統一封裝成河timer類似的api。

開始封裝:


//設置一個timerContainer容器捕獲所有timer,key:timerName ,value:timer
+ (instancetype)scheduledDispatchTimerWithName:(NSString *)timerName
                          timeInterval:(NSTimeInterval)interval
                                 queue:(dispatch_queue_t)queue
                               repeats:(BOOL)repeats
                                action:(dispatch_block_t)action {
    
    NSParameterAssert(timerName);
    GCDTimer *gcdTimer = [[GCDTimer alloc] init];
    if (!queue) {
        queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    dispatch_source_t timer = [gcdTimer.timerContainer objectForKey:timerName];
    if (!timer) {
        timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        [gcdTimer.timerContainer setObject:timer forKey:timerName];
        dispatch_resume(timer);
    }
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
    __weak GCDTimer *weakTimer = gcdTimer;
    dispatch_source_set_event_handler(timer, ^{
        action();
        if (!repeats) {
            [weakTimer cancelTimerWithName:timerName];
        }
    });
    return gcdTimer;
}

- (void)cancelTimerWithName:(NSString *)timerName {
    dispatch_source_t timer = [self.timerContainer objectForKey:timerName];
    if (!timer) {
        return ;
    }
    [self.timerContainer removeObjectForKey:timerName];
    dispatch_source_cancel(timer);
}

外部調用:

//1.外部開始timer
GCDTimer *gcdtimer = [GCDTimer scheduledDispatchTimerWithName:@"myTime"
                                                 timeInterval:2
                                                        queue:dispatch_get_main_queue()
                                                      repeats:YES
                                                       action:^{
                                                           NSLog(@"GCDTimer");
                                                       }];
//2.外部cancelTimer
[gcdtimer cancelTimerWithName@"myTime"];

上面我們弄了個timer,2s內執行一次,在主線程,重復執行,直到你cancel到這個timer,如果repeat為No,那么執行一次就會cancel掉這個timer.

  1. 通過queue我們可以讓timer在不同的線程中執行,queue傳nil默認放入一個子線程中進行。
  2. 我們可以在dealloc中做cancel,這樣保證了timer運行于對象的整個生命周期

這些都是timerperformSelector不具有的優勢。

外部調用簡單明了,不需要關心timer的釋放問題,和所在的線程是不是開啟了runloop,只要注意一下循環引用,也沒有了需要內存管理的風險。最后來波傳送門GCDTimer

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容