iOS開發 • 實例——Hey, 定時器!

在如今不少app中,咱們常常會看到輪播圖,輪播廣告等等,好比淘寶、京東商城app,他們均可以定時循環地播放廣告、圖片,背後的功臣之一就是今天的主角——定時器 NSTimerbash

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

  • 定時器的經常使用方式
  • fire方法的正確理解
  • NSRunloopMode對定時器的影響
  • 子線程開啓定時器
  • GCD定時器
  • 定時器引發的循環引用的解決思路

###定時開始:oop

我建立一個HomeViewController,而後讓他成爲導航控制器的 RootViewController,在HomeViewControllerviewDidLoad調用定時器方法,以下代碼:學習

#import "HomeViewController.h"

@interface HomeViewController ()

@end

@implementation HomeViewController

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

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

@end
複製代碼

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

運行結果.png

複製代碼

還有另一種方式也能夠開啓定時器,那就是調用這個方法: + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;spa

**注意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。運行的結果同上。code

注意3: NSRunLoopCommonModesNSDefaultRunLoopMode優先級使用場景不一樣:通常都是默認模式。cdn

  • 當使用NSTimer的scheduledTimerWithTimeInterval方法時,此時Timer會被加入到當前線程的RunLoop中,且模式是默認的NSDefaultRunLoopMode,若是當前線程就是主線程,也就是UI線程時,某些UI事件,好比UIScrollView的拖動操做,會將RunLoop切換成NSEventTrackingRunLoopMode模式,在這個過程當中,默認的NSDefaultRunLoopMode模式中註冊的事件是不會被執行的,也就是說,此時使用scheduledTimerWithTimeInterval添加到RunLoop中的Timer就不會執行。
  • 因此爲了設置一個不被UI干擾的Timer,使用的模式是:NSRunLoopCommonModes,這個模式等效於NSDefaultRunLoopModeNSEventTrackingRunLoopMode的結合。(參考官方文檔) 代碼以下所示:
- (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方法blog

簡單說說fire方法,fire是火焰的意思,從字面意思能夠聯想到燃料、加速的意思,that’s right![timer fire]——>就是加速計時的意思,咱們經過好比點擊事件,來讓定時器人爲地加速計時,這個比較簡單,這裏就很少贅述。

####GCD定時器

GCD定時器,經過建立隊列、建立資源來實現定時的功能,以下代碼所示: **注意:**若是延遲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
{
    //建立隊列
    dispatch_queue_t queue = 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(@"重複了");
            [self timerAction];
        } 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(), ^{
                [self timerAction];
            });
            dispatch_source_cancel(gcdTimer);
        }
    });
    dispatch_resume(gcdTimer);
}

/*
 定時器的常規用法
 */
- (void)regularTime
{
    //自動開啓
    [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector
    (timerAction) userInfo:nil repeats: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就能夠解決定時器循環引用問題
    __weak typeof(self)weakSelf = self;
    timer =  [NSTimer scheduledTimerWithTimeInterval:1.0f repeats:YES block:^(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也沒法解決定時器循環引用問題
    __weak typeof(self)weakself = self;
    timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:weakself selector:
    @selector(timerAction) userInfo:nil repeats:YES];
}
複製代碼

既然如此,那該如何是好? 答案是:經過類擴展,本身改寫NSTimer的類方法,在控制器中調用新的類方法,直接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
複製代碼

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

#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
複製代碼

運行結果.png

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

我以前在開發app的時候,對定時器更可能是會用的層次,通過此次的深刻學習,對定時器的原理有了更深刻的理解、認識,技術的提高,不少時候都是基礎知識的延伸,對原理理解透徹,不少東西就能夠觸類旁通,所有都通了,但願對本身和各位同道人有所幫助。

相關文章
相關標籤/搜索