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

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

簡(jiǎn)單地介紹了它的應(yīng)用場(chǎng)景,接下來(lái),說(shuō)說(shuō)此次要分享的技能點(diǎn):

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

定時(shí)開(kāi)始:

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

#import "HomeViewController.h"

@interface HomeViewController ()

@end

@implementation HomeViewController

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

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

@end

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


運(yùn)行結(jié)果.png

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

注意1:單獨(dú)地寫(xiě)這一句代碼,默認(rèn)是不會(huì)開(kāi)啟定時(shí)器的,讓我們看看蘋(píng)果官方原文是怎么說(shuō)的:“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.)
也就是說(shuō),需要添加到runloop中,手動(dòng)開(kāi)啟。運(yùn)行的結(jié)果同上。

注意2:在主線程中,runloop是默認(rèn)開(kāi)啟,如果是在子線程中開(kāi)啟開(kāi)啟定時(shí)器,那么我們還需要手動(dòng)開(kāi)啟runloop。運(yùn)行的結(jié)果同上。

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

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

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

fire方法

簡(jiǎn)單說(shuō)說(shuō)fire方法,fire是火焰的意思,從字面意思可以聯(lián)想到燃料、加速的意思,that’s right![timer fire]——>就是加速計(jì)時(shí)的意思,我們通過(guò)比如點(diǎn)擊事件,來(lái)讓定時(shí)器人為地加速計(jì)時(shí),這個(gè)比較簡(jiǎn)單,這里就不多贅述。

GCD定時(shí)器

GCD定時(shí)器,通過(guò)創(chuàng)建隊(duì)列、創(chuàng)建資源來(lái)實(shí)現(xiàn)定時(shí)的功能,如下代碼所示:
注意:如果延遲2秒才開(kāi)啟定時(shí)器,那么dispatch_resume(gcdTimer)必須寫(xiě)在外面。

#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)建隊(duì)列
    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)用這個(gè)方法,釋放定時(shí)器
            //            dispatch_source_cancel(gcdTimer);
            //延遲兩秒會(huì)出現(xiàn)什么情況呢?
            /*
             為何會(huì)調(diào)用兩次?2秒之后再觸發(fā)定時(shí)器后,耽擱了0.001秒去cancel,那定時(shí)器已經(jīng)再次
             觸發(fā)了,所以走了兩次,解決的方法就是把cancel寫(xiě)在外面。
             */
            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);
}

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

- (void)timerAction
{
    NSLog(@"定時(shí)器:%s",__func__);
}
定時(shí)器循環(huán)引用的解決思路
  • 循環(huán)引用出現(xiàn)的場(chǎng)景:
    eg:有兩個(gè)控制器A和B,A 跳轉(zhuǎn)到B中,B開(kāi)啟定時(shí)器,但是當(dāng)我返回A界面時(shí),定時(shí)器依然還在走,控制器也并沒(méi)有執(zhí)行dealloc方法銷毀掉。
  • 為何會(huì)出現(xiàn)循環(huán)引用的情況呢?
    原因是:定時(shí)器對(duì)控制器 (self) 進(jìn)行了強(qiáng)引用。

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

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

第二種思路:

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

既然如此,那該如何是好?
答案是:通過(guò)類擴(kuò)展,自己改寫(xiě)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

類擴(kuò)展寫(xiě)好之后,在控制器中調(diào)用,重寫(xiě)類方法,讓定時(shí)器對(duì)NSTimer類強(qiáng)引用,類是沒(méi)有內(nèi)存空間的,就沒(méi)有循環(huán)引用,跟蘋(píng)果提供的新方法是類似的處理方式,如下代碼和運(yù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(@"定時(shí)器:%s",__func__);
}

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

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

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

總結(jié):

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容