iOS - NSTimer使用須知

NSTimer定時器想必大家都用過,一個很常見的需求:進(jìn)入某個ViewController之后,需要開啟一個定時器,輪詢某個接口以獲取并更新本地數(shù)據(jù)??吹竭@種需求,腦海里下意識就有了想法,-viewDidLoad里創(chuàng)建一個NSTimer,然后-dealloc里結(jié)束掉它,ok看代碼:

@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fire) userInfo:nil repeats:YES];
}

- (void)fire {
    NSLog(@"定時器調(diào)用");
}

- (void)dealloc {
    [self.timer invalidate];
    NSLog(@"dealloc - ViewController");
}

@end

搞定,跑一下看看。fire方法每隔一秒被調(diào)用一次,完美?。?!返回退掉控制器,,,誒,-fire方法怎么還在調(diào)用?控制器沒被釋放???!姿勢不對嗎?

看NSTimer的創(chuàng)建方法
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fire) userInfo:nil repeats:YES];
target傳的是self,這里應(yīng)該有一個強(qiáng)引用;
再看@property (nonatomic, strong) NSTimer *timer;
這里用的strong,嗦嘎!循環(huán)引用嘛!簡單,換weak破掉它!
重新跑驗(yàn)證看看??!進(jìn)入控制器,fire方法正常被調(diào)用;退出看看,,,怎么還在調(diào)用??!

.
.
.

沒轍,只好翻蘋果官方文檔了。Xcode工具欄找到Help->Developer Documentation,打開之后直接搜索NSTimer,進(jìn)去看看,找到這么一段:


創(chuàng)建

停止

看到一個關(guān)鍵詞:run loop。原來這個定時器還牽扯到了運(yùn)行循環(huán),看來關(guān)健點(diǎn)是將定時器移出這個run loop。(即調(diào)用-invalidate
我們在控制器的-dealloc里調(diào)用-invalidate,很顯然dealloc沒有執(zhí)行。
那么換個地方調(diào)用唄!退出控制器時有幾個方法會被調(diào)用:

  • -viewWillDisappear:
    如果是進(jìn)入下級頁面,這個定時器不應(yīng)該被停掉,所以這里不好。
  • -willMoveToParentViewController:
    當(dāng)parent為nil時,表示控制器被移除了,此時停掉的時機(jī)正好。
- (void)willMoveToParentViewController:(UIViewController *)parent {
    if(!parent){
        [self.timer invalidate];
    }
}

再跑一下看看!進(jìn)入控制器,fire方法被定時調(diào)用;退出控制器,dealloc方法打印了!fire方法停止調(diào)用,控制器釋放!搞定收工??!

.
.
.

等等!還有其它解決方式嗎?不在dealloc里停掉定時器總感覺怪怪的。。。(代碼強(qiáng)迫癥患者求治療~~)

繼續(xù)分析:
既然一定要在控制器的dealloc里停掉定時器,那么控制器就不該被定時器強(qiáng)引用。還記得創(chuàng)建定時器的時候傳的target嗎?

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fire) userInfo:nil repeats:YES];

我們在target這里作文章:你是強(qiáng)引用吧?我不給你控制器對象,再創(chuàng)建一個對象給你。這樣,控制器沒有被強(qiáng)引用,也就不會影響dealloc的調(diào)用,接著定時器就會被停掉。
但是還有一個問題:你新創(chuàng)建的對象怎么執(zhí)行我控制器里的fire方法????
.
.
.
消息轉(zhuǎn)發(fā),看代碼:

@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    HHTimerProxy *proxy = [HHTimerProxy alloc];
    proxy.target = self;//這里是weak,不影響控制器的生命周期

    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(fire) userInfo:nil repeats:YES];
}

- (void)fire {
    NSLog(@"定時器調(diào)用");
}

- (void)dealloc {
    [self.timer invalidate];
    NSLog(@"dealloc - ViewController");
}

@end
//HHTimerProxy.h
//這個類只用作消息轉(zhuǎn)發(fā)
@interface HHTimerProxy : NSProxy
@property (nonatomic, weak) id target;
@end

//HHTimerProxy.m
@implementation HHTimerProxy

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

@end

重新運(yùn)行,進(jìn)入控制器,fire方法正常調(diào)用;退出控制器,dealloc方法打印,定時器停止調(diào)用fire方法,控制器正常釋放。問題解決。

不過,再想想這里的使用姿勢:我就用個定時器,還得記著創(chuàng)建一個proxy對象,然后把控制器傳給proxy的target屬性,再將這個proxy傳給定時器的target。。好麻煩不是嗎?

封裝走一波???

// NSTimer+HH.h
@interface NSTimer (HH)
+ (NSTimer *)HH_scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(__nullable id)userInfo repeats:(BOOL)yesOrNo;
@end

// NSTimer+HH.m
@implementation NSTimer (HH)
+ (NSTimer *)HH_scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
    HHTimerProxy *proxy = [HHTimerProxy alloc];
    proxy.target = aTarget;    
    return [NSTimer scheduledTimerWithTimeInterval:ti target:proxy selector:@selector(fire) userInfo:userInfo repeats:YES];
}

這樣,以后用到定時器就這么干:

#import "NSTimer+HH.h"

@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.timer = [NSTimer HH_scheduledTimerWithTimeInterval:1 target:self selector:@selector(fire) userInfo:nil repeats:YES];
}

- (void)fire {
    NSLog(@"定時器調(diào)用");
}

- (void)dealloc {
    [self.timer invalidate];
    NSLog(@"dealloc - ViewController");
}

@end

依舊如此完美!

.
.
.

不過,你以為這樣就完了嗎??太天真了!

iOS10.0+有了block的調(diào)用姿勢,用起來很方便,代碼也緊湊,搞一下?

.
.
.

未完待續(xù)...

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

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

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