在現(xiàn)在很多app中,我們經(jīng)常會看到輪播圖,輪播廣告等等,比如淘寶、京東商城app,他們都可以定時循環(huán)地播放廣告、圖片,背后的功臣之一就是今天的主角——定時器 NSTimer。
簡單地介紹了它的應(yīng)用場景,接下來,說說此次要分享的技能點:
- 定時器的常用方式
- fire方法的正確理解
- NSRunloopMode對定時器的影響
- 子線程開啟定時器
- GCD定時器
- 定時器引起的循環(huán)引用的解決思路
定時開始:
我創(chuàng)建一個
HomeViewController,然后讓他成為導航控制器的
RootViewController,在HomeViewController的viewDidLoad調(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:單獨地寫這一句代碼,默認是不會開啟定時器的,讓我們看看蘋果官方原文是怎么說的:“
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是默認開啟,如果是在子線程中開啟開啟定時器,那么我們還需要手動開啟runloop。運行的結(jié)果同上。
注意3: NSRunLoopCommonModes和NSDefaultRunLoopMode優(yōu)先級使用場景不同:一般都是默認模式。
- 當使用NSTimer的
scheduledTimerWithTimeInterval方法時,此時Timer會被加入到當前線程的RunLoop中,且模式是默認的NSDefaultRunLoopMode,如果當前線程就是主線程,也就是UI線程時,某些UI事件,比如UIScrollView的拖動操作,會將RunLoop切換成NSEventTrackingRunLoopMode模式,在這個過程中,默認的NSDefaultRunLoopMode模式中注冊的事件是不會被執(zhí)行的,也就是說,此時使用scheduledTimerWithTimeInterval添加到RunLoop中的Timer就不會執(zhí)行。 - 所以為了設(shè)置一個不被UI干擾的Timer,使用的模式是:
NSRunLoopCommonModes,這個模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結(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(@"重復了");
[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開啟定時器,但是當我返回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)過這次的深入學習,對定時器的原理有了更深入的理解、認識,技術(shù)的提升,很多時候都是基礎(chǔ)知識的延伸,對原理理解透徹,很多東西就可以舉一反三,全部都通了,希望對自己和各位同道人有所幫助。

