NSTimer在時長開發中使用頻率仍是比較高的,但一個不注意可能就會形成了小問題,平常使用中還需多注意纔是。bash
問:NSTimer計時準確嗎?app
答:NSTimer 若是以下使用通常不許確:oop
//在主線程中調用
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(logInfo) userInfo:nil repeats:YES];
//或者 在主線程中調用
_timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(logInfo) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
複製代碼
以上兩種方式直接調用NSTimer時極可能會計時不許確,緣由以下:測試
NSTimer在每一次的runloop中會被處理,但當runloop中有其餘比較多的耗時操做,且操做時間超過了NSTimer的間隔,那麼這一次的NSTimer就會被延後處理。致使不許確。 解決辦法:能夠將NSTimer放入子線程,並手動開啓子線程的runloop。當前runloop中沒有其餘耗時操做,因此也會相對準確一些。ui
runloop model 模式的影響,當沒有指定時,runloop默認會添加到RunLoopDefaultMode
中,當頁面有tableview滑動時,主線程的runloop會切換到TrackingRunLoopMode
,此模式下,NSTimer不會被觸發,致使計時不許確。spa
NSTimer涉及到內存泄漏,主要是指在Timer中repeats
爲YES
,即須要間隔指定時間,重複調用方法,此時須要手動調用[self.timer invalidate]
使定時器失效。線程
若是repeats
爲NO
,則不存在內存泄漏問題,調用完成後,定時器會自動失效。code
通常使用NSTimer
時,用法以下:cdn
self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
複製代碼
上面代碼這樣寫,定時器會正常調用,但當退出當前頁面或者功能以後,定時器依然在調用,並無中止。想要中止,須要手動調用[self.timer invalidate]
方法。對象
可能想到下面這種寫法:
- (void)dealloc
{
[self.timer invalidate];
self.timer = nil;
}
複製代碼
這種寫法咋一看沒有問題,不是要手動調用中止定時器的方法嘛,那在頁面釋放的時候,調用就行了。
通過測試,能夠發現這樣寫的話,當前對象self
不會釋放,定時器也沒有釋放,這就形成了內存泄漏。
再看一下以前的代碼:
- (void)dealloc
{
[self.timer invalidate];
self.timer = nil;
}
self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
複製代碼
self
持有了timer
, 而timer
在target
參數裏持有了self
這就致使了相互引用。
可能有的同窗立馬就有了疑惑:
self
不持有timer
也能夠正常使用,爲何要持有timer
爲何會持有self
針對以上兩個問題答疑:
self
持有timer
是由於須要在其餘的地方手動調用[self.timer invalidate]
,self
不持有timer
確實能夠簡單解決相互引用問題,但timer
卻沒法手動釋放。runloop
須要對timer
的觀察者作保留操做,以便後續指定的時間點來到時作指定操做。解決上面的內存泄漏問題,大體有下面幾種辦法:
viewWillDisappear:
手動中止scheduledTimerWithTimeInterval:repeats:block
方法NSTimer
和當前調用對象之間的循環引用。詳細解釋:
手動中止定時器,要有一個時機。若是根據需求,恰好能夠在用戶操做某項功能時能夠主動中止,算是一個比較好的辦法。但若是沒有這個時機則能夠在一、2兩個方法中解決。
viewWillDisappear:
手動中止
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
複製代碼
須要注意在這個方法中中止,適用於不會從當前頁面push出下一個頁面的狀況,由於若是push出下一個頁面,也會調用viewWillDisappear:
,可能就得不到正確的結果。
重寫返回方法手動中止
- (void)backButtonPressed:(id)sender {
[self.timer invalidate];
self.timer = nil;
}
複製代碼
蘋果估計也發現了timer容易致使內存泄漏的問題,因此在iOS10以後,出了一個新的API,使用新API是沒有內存泄漏的。
if (@available(iOS 10.0, *)) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"do more thing");
}];
}
- (void)dealloc
{
[self.timer invalidate];
self.timer = nil;
}
複製代碼
調用這個新的API則不會相互強引用,dealloc
方法會正常調用。
去除NSTimer
和當前調用對象之間的循環引用。 從3中的系統方法其實能夠獲得啓發,3中的方法裏並無直接引用self
,而是讓timer引用了其餘的對象,這樣就解除了相互引用。 對於iOS10一下的系統,能夠模仿一下系統的實現。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSTimer (weak)
+ (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void(^)(NSTimer *timer))block;
@end
NS_ASSUME_NONNULL_END
複製代碼
#import "NSTimer+weak.h"
@implementation NSTimer (weak)
+ (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block{
return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(weak_blcokInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)weak_blcokInvoke:(NSTimer *)timer {
void (^block)(NSTimer *timer) = timer.userInfo;
if (block) {
block(timer);
}
}
@end
複製代碼
在使用時,能夠以下:
self.timer = [NSTimer weak_scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
//do some thing
}];
- (void)dealloc
{
[self.timer invalidate];
self.timer = nil;
}
複製代碼
能夠看把target由以前原始的vc對象,轉換成了timer對象,從而打破了雙向引用。