iOS小知識之NSTimer的迴圈引用一
小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。
1.NSTimer的迴圈引用
1.1 常見問題
日常開發中,經常會用到NSTimer
定時器,一些不正確的寫法,會導致NSTimer
造成迴圈引用,如下程式碼所示
``` - (void)viewDidLoad { [super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
``
上述案例,一定會產生迴圈引用
- 建立
NSTimer時,將
self傳入
target,導致
NSTimer持有
self,而
self又持有
timer- 使用
NSTimer的
invalidate方法,可以解除
NSTimer對
self的持有
- 但是案例中,
NSTimer的
invalidate方法,由
UIViewController的
dealloc方法執行。但是
self被
timer持有,只要
timer有效,
UIViewController的
dealloc`方法就不會執行。故此雙方相互等待,造成迴圈引用
1.2 target傳入弱引用物件
對NSTimer
的target
引數傳入一個弱引用的self
,能否打破對self
的強持有?
程式碼如下:
``` - (void)viewDidLoad { [super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
``
- 肯定是不行的,因為在
timerWithTimeInterval內部,使用強引用物件接收
target`引數,所以外部定義為弱引用物件沒有任何意義
這種方式類似於以下程式碼:
__weak typeof(self) weakSelf = self;
typeof(self) strongSelf = weakSelf;
在官方文件中,對target
引數進行了明確說明:
-
target
:定時器觸發時指定的訊息傳送到的物件。計時器維護對該物件的強引用,直到它(計時器)失效
和Block的區別: Block將捕獲到的弱引用物件,賦值給一個強引用的臨時變數,當Block執行完畢,臨時變數會自動銷燬,解除對外部變數的持有。
2.常規解決方案
2.1 更換API
使用攜帶Block
的方法建立NSTimer
,避免target
的強持有
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
2.2 在適當時機呼叫invalidate
根據業務需求,可以將NSTimer
的invalidate
方法寫在viewWillDisappear
方法中
``` - (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
} - (void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
``
或者寫在
didMoveTo ParentViewController`方法中
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
}
}
- iOS之資料結構與演算法面試題2
- iOS之字串拷貝
- iOS之動態庫與靜態庫的實戰配置
- iOS之Dispatch Semaphore與NSThread runloop實現常駐執行緒
- iOS之xcconfig編寫指南
- iOS之OC與JS的互動(iOS與H5混編)
- iOS之認識Shell2-常用的命令參考1
- iOS之認識Shell-1
- iOS開發中Mach-O的體積優化2
- iOS開發中Mach-O的體積優化
- iOS中runtime如何實現weak變數的自動置nil和SideTable是什麼?
- iOS中的迴圈引用
- iOS之網路相關4:TCP和UDP
- iOS之網路相關3:HTTPS
- iOS之網路相關2:HTTP的通訊過程
- iOS之網路相關1:HTTP協議
- iOS逆向探究3:狀態暫存器
- iOS逆向探究2:彙編初探2
- iOS逆向探究1:彙編初探1
- iOS小知識之底層問題探索