iOS開發(fā) ? 實例——Hey, 定時器!

在現(xiàn)在很多app中宴胧,我們經(jīng)常會看到輪播圖漱抓,輪播廣告等等,比如淘寶恕齐、京東商城app辽旋,他們都可以定時循環(huán)地播放廣告、圖片檐迟,背后的功臣之一就是今天的主角——定時器 NSTimer

簡單地介紹了它的應(yīng)用場景码耐,接下來追迟,說說此次要分享的技能點:

  • 定時器的常用方式
  • fire方法的正確理解
  • NSRunloopMode對定時器的影響
  • 子線程開啟定時器
  • GCD定時器
  • 定時器引起的循環(huán)引用的解決思路

定時開始:

我創(chuàng)建一個HomeViewController,然后讓他成為導(dǎo)航控制器的
RootViewController骚腥,在HomeViewControllerviewDidLoad調(diào)用定時器方法敦间,如下代碼:

#import "HomeViewController.h"

@interface HomeViewController ()

@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self regularTime];
}
/*
 定時器的常規(guī)用法
 */
- (void)regularTime
{
    //自動開啟
    [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    
}

- (void)timerAction
{
    NSLog(@"定時器:%s",__func__);
}

@end

運行結(jié)果:每隔一秒就打印一次。


運行結(jié)果.png

還有另外一種方式也可以開啟定時器束铭,那就是調(diào)用這個方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

注意1:單獨地寫這一句代碼廓块,默認(rèn)是不會開啟定時器的,讓我們看看蘋果官方原文是怎么說的:“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中带猴,手動開啟。運行的結(jié)果同上懈万。

注意2:在主線程中拴清,runloop是默認(rèn)開啟,如果是在子線程中開啟開啟定時器会通,那么我們還需要手動開啟runloop口予。運行的結(jié)果同上。

注意3: NSRunLoopCommonModesNSDefaultRunLoopMode優(yōu)先級使用場景不同:一般都是默認(rèn)模式涕侈。

  • 當(dāng)使用NSTimer的scheduledTimerWithTimeInterval方法時沪停,此時Timer會被加入到當(dāng)前線程的RunLoop中,且模式是默認(rèn)的NSDefaultRunLoopMode裳涛,如果當(dāng)前線程就是主線程木张,也就是UI線程時,某些UI事件调违,比如UIScrollView的拖動操作窟哺,會將RunLoop切換成NSEventTrackingRunLoopMode模式,在這個過程中技肩,默認(rèn)的NSDefaultRunLoopMode模式中注冊的事件是不會被執(zhí)行的且轨,也就是說浮声,此時使用scheduledTimerWithTimeInterval添加到RunLoop中的Timer就不會執(zhí)行。
  • 所以為了設(shè)置一個不被UI干擾的Timer旋奢,使用的模式是:NSRunLoopCommonModes泳挥,這個模式等效于NSDefaultRunLoopModeNSEventTrackingRunLoopMode的結(jié)合。(參考官方文檔)
    代碼如下所示:
- (void)viewDidLoad {
    [super viewDidLoad];
   //在主線程中開啟定時器
    [self regularTime];
   //在子線程中開啟定時器
//    [NSThread detachNewThreadWithBlock:^{
//     NSLog(@"%@",[NSThread currentThread]);
//     [self regularTime];
//    }];
}
- (void)regularTime
{
    timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    //runloop中添加定時器
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//在子線程中啟用定時器必須開啟runloop
//    [[NSRunLoop currentRunLoop] run];
}

- (void)timerAction
{
    NSLog(@"定時器:%s",__func__);
}

fire方法

簡單說說fire方法至朗,fire是火焰的意思屉符,從字面意思可以聯(lián)想到燃料、加速的意思锹引,that’s right矗钟![timer fire]——>就是加速計時的意思,我們通過比如點擊事件嫌变,來讓定時器人為地加速計時吨艇,這個比較簡單,這里就不多贅述腾啥。

GCD定時器

GCD定時器东涡,通過創(chuàng)建隊列、創(chuàng)建資源來實現(xiàn)定時的功能倘待,如下代碼所示:
注意:如果延遲2秒才開啟定時器疮跑,那么dispatch_resume(gcdTimer)必須寫在外面。

#import "HomeViewController.h"

@interface HomeViewController ()
{
    NSTimer * timer;
}

@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self gcdTimer:1 repeats:YES];
}

- (void)gcdTimer:(int)timerInterVal repeats:(BOOL)repeat
{
    //創(chuàng)建隊列
    dispatch_queue_t queue = dispatch_queue_create("my queue", 0);
    //創(chuàng)建資源
    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(@"重復(fù)了");
            [self timerAction];
        } else
        {
            //            [self timerAction];
            //            //調(diào)用這個方法凸舵,釋放定時器
            //            dispatch_source_cancel(gcdTimer);
            //延遲兩秒會出現(xiàn)什么情況呢祖娘?
            /*
             為何會調(diào)用兩次?2秒之后再觸發(fā)定時器后贞间,耽擱了0.001秒去cancel贿条,那定時器已經(jīng)再次
             觸發(fā)了,所以走了兩次增热,解決的方法就是把cancel寫在外面整以。
             */
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)
            (timerInterVal*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self timerAction];
            });
            dispatch_source_cancel(gcdTimer);
        }
    });
    dispatch_resume(gcdTimer);
}

/*
 定時器的常規(guī)用法
 */
- (void)regularTime
{
    //自動開啟
    [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector
    (timerAction) userInfo:nil repeats:YES];
    
}

- (void)timerAction
{
    NSLog(@"定時器:%s",__func__);
}
定時器循環(huán)引用的解決思路
  • 循環(huán)引用出現(xiàn)的場景:
    eg:有兩個控制器A和B费彼,A 跳轉(zhuǎn)到B中汛骂,B開啟定時器蜕劝,但是當(dāng)我返回A界面時磷斧,定時器依然還在走择膝,控制器也并沒有執(zhí)行dealloc方法銷毀掉昨凡。
  • 為何會出現(xiàn)循環(huán)引用的情況呢导街?
    原因是:定時器對控制器 (self) 進行了強引用耻蛇。

先說簡單的解決思路:
蘋果官方為了給我們解決這個循環(huán)引用的問題吭从,提供了一個定時器的新的自帶方法:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
代碼如下:

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

第二種思路:

  • 既然引發(fā)循環(huán)引用是因為Timer對self的強引用朝蜘,那我讓Timer不對self強引用,不就解決了涩金。
  • 本人非常興奮地實驗了兩種方法:timer=nil(失敗)谱醇、__weak typeof(self)weakself = self(失敗)暇仲,都是調(diào)用系統(tǒng)自帶的方法,如下代碼:
- (void)regularTime
{
    //自動開啟
    //timer置為nil或者__weak typeof(self)weakself = self也無法解決定時器循環(huán)引用問題
    __weak typeof(self)weakself = self;
    timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:weakself selector:
    @selector(timerAction) userInfo:nil repeats:YES];
}

既然如此副渴,那該如何是好奈附?
答案是:通過類擴展,自己改寫NSTimer的類方法煮剧,在控制器中調(diào)用新的類方法斥滤,直接show the code:


NSTimer+HomeTimer.h中:

#import <Foundation/Foundation.h>

@interface NSTimer (HomeTimer)

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

NSTimer+HomeTimer.m中:

#import "NSTimer+HomeTimer.h"

@implementation NSTimer (HomeTimer)

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)timerInterval block:(void(^)())block repeats:(BOOL)repeat
{
    return [self timerWithTimeInterval:timerInterval target:self selector:@selector(timerAction:) userInfo:block repeats:YES];
}

+ (void)timerAction:(NSTimer *)timer
{
    void (^block)() = [timer userInfo];
    if (block) {
        block();
    }
}
@end

類擴展寫好之后,在控制器中調(diào)用勉盅,重寫類方法佑颇,讓定時器對NSTimer類強引用,類是沒有內(nèi)存空間的草娜,就沒有循環(huán)引用漩符,跟蘋果提供的新方法是類似的處理方式,如下代碼和運行結(jié)果所示:

#import "HomeTimerViewController.h"
#import "NSTimer+HomeTimer.h"

@interface HomeTimerViewController ()
{
    NSTimer * timer;
}
@end

@implementation HomeTimerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self regularTime];
    self.view.backgroundColor = [UIColor greenColor];
}

- (void)regularTime
{
    __weak typeof(self)weakSelf = self;
    timer = [NSTimer timerWithTimeInterval:1.0f block:^{
        [weakSelf timerAction];
    } repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

- (void)timerAction
{
    NSLog(@"定時器:%s",__func__);
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
}

@end
運行結(jié)果.png

定時結(jié)束:用時2小時32分鐘

總結(jié):

我之前在開發(fā)app的時候驱还,對定時器更多是會用的層次,經(jīng)過這次的深入學(xué)習(xí)凸克,對定時器的原理有了更深入的理解议蟆、認(rèn)識,技術(shù)的提升萎战,很多時候都是基礎(chǔ)知識的延伸咐容,對原理理解透徹,很多東西就可以舉一反三蚂维,全部都通了戳粒,希望對自己和各位同道人有所幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虫啥,一起剝皮案震驚了整個濱河市蔚约,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涂籽,老刑警劉巖苹祟,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異评雌,居然都是意外死亡树枫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門景东,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砂轻,“玉大人,你說我怎么就攤上這事斤吐∩裕” “怎么了厨喂?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長体谒。 經(jīng)常有香客問我杯聚,道長,這世上最難降的妖魔是什么抒痒? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任幌绍,我火速辦了婚禮,結(jié)果婚禮上故响,老公的妹妹穿的比我還像新娘傀广。我一直安慰自己,他們只是感情好彩届,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布伪冰。 她就那樣靜靜地躺著,像睡著了一般樟蠕。 火紅的嫁衣襯著肌膚如雪贮聂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天寨辩,我揣著相機與錄音吓懈,去河邊找鬼。 笑死靡狞,一個胖子當(dāng)著我的面吹牛耻警,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甸怕,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼甘穿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梢杭?” 一聲冷哼從身側(cè)響起温兼,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎武契,沒想到半個月后妨托,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡吝羞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年兰伤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钧排。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡敦腔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恨溜,到底是詐尸還是另有隱情符衔,我是刑警寧澤找前,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站判族,受9級特大地震影響躺盛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜形帮,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一槽惫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辩撑,春花似錦界斜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至君躺,卻和暖如春峭判,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棕叫。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工朝抖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谍珊。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像急侥,于是被迫代替她去往敵國和親砌滞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容