NSTimer 进阶使用总结与注意事项
生活随笔
收集整理的這篇文章主要介紹了
NSTimer 进阶使用总结与注意事项
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
NSTimer 是 iOS 上的一種計(jì)時器,通過 NSTimer 對象,可以指定時間間隔,向一個對象發(fā)送消息。NSTimer 是比較常用的工具,比如用來定時更新界面,定時發(fā)送請求等等。但是在使用過程中,有很多需要注意的地方,稍微不注意就會產(chǎn)生 bug,crash,內(nèi)存泄漏。本文講解了使用 NSTimer 時需要注意的問題。
self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; 上述代碼,將創(chuàng)建一個無限循環(huán)的 timer,并投入當(dāng)前線程的 Runloop 中開始執(zhí)行。此時,Runloop 會引用住 timer,timer 會引用住 self,self 則保存了 timer。如下圖所示:
需要注意的是,這種無限循環(huán)的 timer,會一直執(zhí)行,需要調(diào)用 [timer invalidate] 顯式停止。否則 runloop 會一直引用著 timer,timer 又引用了 self,導(dǎo)致 self 整個對象泄漏,實(shí)際情況中,這個 self 有可能是一個 view,甚至是一個 controller。
那, [timer invalidate] 要什么時候調(diào)用?
有些人會在 self 的 dealloc 里面調(diào)用,這幾乎可以確定是錯誤的。因?yàn)?timer 會引用住 self,在 timer 停止之前,是不會釋放 self 的,self 的 dealloc 也不可能會被調(diào)用。
正確的做法應(yīng)該是根據(jù)業(yè)務(wù)需要,在適當(dāng)?shù)牡胤絾?timer 和 停止 timer。比如 timer 是頁面用來更新頁面內(nèi)部的 view 的,那可以選擇在頁面顯示的時候啟動 timer,頁面不可見的時候停止 timer。比如:
- (void)viewWillAppear {[super viewWillAppear];self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; }- (void)viewDidDisappear {[super viewDidDisappear];[self.timer invalidate]; }
錯誤特征 1: - (void)dealloc {[self.timer invalidate]; } 以上代碼是有問題的。當(dāng) timer 沒有停止的時候,self 會被引用,也就沒有機(jī)會走到 dealloc。同時,代碼作者應(yīng)該對 timer 沒有正確的認(rèn)識,所以需要 review 整個 timer 的使用情況。
錯誤特征 2: [NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; 以上代碼創(chuàng)建了一個 timer,但是沒有保存起來,后續(xù)自然也沒有機(jī)會停止這個 timer。所以會導(dǎo)致 timer 泄漏。
錯誤特征 3: - (void)viewDidAppear:(BOOL)animated {[super viewDidAppear:animated];self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; } 以上代碼也是有問題的。因?yàn)槲覀円_保 timer 的創(chuàng)建和銷毀必須是成對調(diào)用,否則會發(fā)生泄漏。而對于 viewDidAppear 其實(shí)很難找到一個準(zhǔn)確的與之成對的方法(跟 viewWillDisappear 和 viewDidDisappear 都不是成對調(diào)用的),這里就需要檢查 Timer 有沒有被重復(fù)創(chuàng)建和有沒有在適當(dāng)?shù)臅r機(jī)銷毀。
- (void)onEnterBackground:(id)sender {[self.timer invalidate];[self.view stopAnimation]; // dangerous! } 以上代碼,加入第一行的 invalidate 之后,self 被銷毀了,那么第二行訪問 self.view 時候,就會觸發(fā)野指針 crash。因?yàn)?Objective-C 的方法里面,self 是沒有被 retain 的。這種情況,有個臨時的解決方案如下:
- (void)onEnterBackground:(id)sender {__weak id weakSelf = self;[self.timer invalidate];[weakSelf.view stopAnimation]; // dangerous! } 將 self 改為弱引用。但是也是一個臨時解決方案。正確解決方法是,查出其它對象沒有引用 self 的時候,為什么 timer 還沒停止。這個案例告訴大家,當(dāng)見到 invalidate 被調(diào)用之后很神奇地出現(xiàn)了 self 野指針 crash 的時候,不要驚訝,就是 timer 沒處理好。
Weak Timer 的實(shí)現(xiàn)方式分為兩種,第一種是在 NSTimer 和 target 中間加多一層代理(Proxy),代理作為 target 被 NSTimer 強(qiáng)引用,同時弱引用真正的 target,并對它轉(zhuǎn)發(fā)消息。示例圖如下:
+ (NSTimer *)qz_scheduledWeakTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {QzoneWeakProxy *proxy = [[QzoneWeakProxy weakProxyForObject:target];return [self scheduledTimerWithTimeInterval:ti target:proxy selector:aSelector userInfo:userInfo repeats:repeats]; } 第二種方案是用 dispatch timer 自己實(shí)現(xiàn)一遍 timer,具體實(shí)現(xiàn)里面,弱引用 target。
比如這個: https://github.com/mindsnacks/MSWeakTimer。
1. NSTimer 容易泄漏
比如以下代碼創(chuàng)建了一個計(jì)時器:self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; 上述代碼,將創(chuàng)建一個無限循環(huán)的 timer,并投入當(dāng)前線程的 Runloop 中開始執(zhí)行。此時,Runloop 會引用住 timer,timer 會引用住 self,self 則保存了 timer。如下圖所示:
需要注意的是,這種無限循環(huán)的 timer,會一直執(zhí)行,需要調(diào)用 [timer invalidate] 顯式停止。否則 runloop 會一直引用著 timer,timer 又引用了 self,導(dǎo)致 self 整個對象泄漏,實(shí)際情況中,這個 self 有可能是一個 view,甚至是一個 controller。
那, [timer invalidate] 要什么時候調(diào)用?
有些人會在 self 的 dealloc 里面調(diào)用,這幾乎可以確定是錯誤的。因?yàn)?timer 會引用住 self,在 timer 停止之前,是不會釋放 self 的,self 的 dealloc 也不可能會被調(diào)用。
正確的做法應(yīng)該是根據(jù)業(yè)務(wù)需要,在適當(dāng)?shù)牡胤絾?timer 和 停止 timer。比如 timer 是頁面用來更新頁面內(nèi)部的 view 的,那可以選擇在頁面顯示的時候啟動 timer,頁面不可見的時候停止 timer。比如:
- (void)viewWillAppear {[super viewWillAppear];self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; }- (void)viewDidDisappear {[super viewDidDisappear];[self.timer invalidate]; }
2. 錯誤特征
實(shí)際開發(fā)中,或者 Code Review 的時候,可以通過一些特征初步判定可能會有問題。錯誤特征 1: - (void)dealloc {[self.timer invalidate]; } 以上代碼是有問題的。當(dāng) timer 沒有停止的時候,self 會被引用,也就沒有機(jī)會走到 dealloc。同時,代碼作者應(yīng)該對 timer 沒有正確的認(rèn)識,所以需要 review 整個 timer 的使用情況。
錯誤特征 2: [NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; 以上代碼創(chuàng)建了一個 timer,但是沒有保存起來,后續(xù)自然也沒有機(jī)會停止這個 timer。所以會導(dǎo)致 timer 泄漏。
錯誤特征 3: - (void)viewDidAppear:(BOOL)animated {[super viewDidAppear:animated];self.timer =[NSTimer scheduledTimerWithTimeInterval:1target:selfselector:@selector(update)userInfo:nilrepeats:YES]; } 以上代碼也是有問題的。因?yàn)槲覀円_保 timer 的創(chuàng)建和銷毀必須是成對調(diào)用,否則會發(fā)生泄漏。而對于 viewDidAppear 其實(shí)很難找到一個準(zhǔn)確的與之成對的方法(跟 viewWillDisappear 和 viewDidDisappear 都不是成對調(diào)用的),這里就需要檢查 Timer 有沒有被重復(fù)創(chuàng)建和有沒有在適當(dāng)?shù)臅r機(jī)銷毀。
3. 停止 timer 可能會導(dǎo)致 self 對象銷毀
值得注意的是,調(diào)用 [timer invalidate] 停止 timer,此時 timer 會釋放 target,如果 timer 是最后一個持有 target 的對象,那么此次釋放會直接觸發(fā) target 的 。比如:- (void)onEnterBackground:(id)sender {[self.timer invalidate];[self.view stopAnimation]; // dangerous! } 以上代碼,加入第一行的 invalidate 之后,self 被銷毀了,那么第二行訪問 self.view 時候,就會觸發(fā)野指針 crash。因?yàn)?Objective-C 的方法里面,self 是沒有被 retain 的。這種情況,有個臨時的解決方案如下:
- (void)onEnterBackground:(id)sender {__weak id weakSelf = self;[self.timer invalidate];[weakSelf.view stopAnimation]; // dangerous! } 將 self 改為弱引用。但是也是一個臨時解決方案。正確解決方法是,查出其它對象沒有引用 self 的時候,為什么 timer 還沒停止。這個案例告訴大家,當(dāng)見到 invalidate 被調(diào)用之后很神奇地出現(xiàn)了 self 野指針 crash 的時候,不要驚訝,就是 timer 沒處理好。
4. Perform Delay
[NSObject performSelector:withObject:afterDelay:] 和 [NSObject performSelector:withObject:afterDelay:inMode:] 我們簡稱為 Perform Delay,他們的實(shí)現(xiàn)原理就是一個不循環(huán)(repeat 為 NO)的 timer。所以使用這兩個接口的注意事項(xiàng)跟使用 timer 類似。需要在適當(dāng)?shù)牡胤秸{(diào)用 [NSObject cancelPreviousPerformRequestsWithTarget:selector:object:]5. Runloop Mode
注意創(chuàng)建 NSTimer 或者調(diào)用 Perform Delay 方法,都是往當(dāng)前線程的 Runloop 中投遞消息,大部分接口的默認(rèn)投遞模式是 CFRunloopDefaultMode。也就是說,Runloop 不在 DefaultMode 下運(yùn)行的時候(比如滾動列表的時候主線程的 runloop mode 是 CFRunloopTrackingMode),消息將被暫時阻塞,不能及時處理。6. Weak Timer
NSTimer 之所以比較難用對,比較重要的原因主要是 NSTimer 對 target 是強(qiáng)引用的。這導(dǎo)致了 target 泄漏,或者生命周期超出開發(fā)者的預(yù)期。timer 如果對 target 是弱引用的話,這些問題就不存在了,這就是 Weak Timer。Weak Timer 的實(shí)現(xiàn)方式分為兩種,第一種是在 NSTimer 和 target 中間加多一層代理(Proxy),代理作為 target 被 NSTimer 強(qiáng)引用,同時弱引用真正的 target,并對它轉(zhuǎn)發(fā)消息。示例圖如下:
+ (NSTimer *)qz_scheduledWeakTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {QzoneWeakProxy *proxy = [[QzoneWeakProxy weakProxyForObject:target];return [self scheduledTimerWithTimeInterval:ti target:proxy selector:aSelector userInfo:userInfo repeats:repeats]; } 第二種方案是用 dispatch timer 自己實(shí)現(xiàn)一遍 timer,具體實(shí)現(xiàn)里面,弱引用 target。
比如這個: https://github.com/mindsnacks/MSWeakTimer。
總結(jié)
以上是生活随笔為你收集整理的NSTimer 进阶使用总结与注意事项的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Objective-C 深复制和浅复制与
- 下一篇: 看YYModel源码的一些收获