總結(jié)一下平時使用NSTimer碰到的幾個小問題:
- 作為定時器使用時不準(zhǔn)確的問題
- ScrollView滾動時timer不執(zhí)行的問題
- 內(nèi)存泄漏的問題
NSTimer準(zhǔn)確性不高的原因
以上幾個問題都跟RunLoop
有關(guān)系敢靡,一個 NSTimer
注冊到RunLoop
后隙轻,RunLoop
會為其重復(fù)的時間點(diǎn)注冊好事件滚粟。例如 10:00, 10:10, 10:20 這幾個時間點(diǎn)漾橙。RunLoop
為了節(jié)省資源斋枢,并不會在非常準(zhǔn)確的時間點(diǎn)回調(diào)這個Timer誊册。Timer 有個屬性叫做 Tolerance (寬容度)
,標(biāo)示了當(dāng)時間點(diǎn)到后笋敞,容許有多少最大誤差。
如果某個時間點(diǎn)被錯過了挪凑,例如執(zhí)行了一個很長的任務(wù)孕索,則那個時間點(diǎn)的回調(diào)也會跳過去,不會延后執(zhí)行躏碳。就比如等公交搞旭,如果 10:10 時我忙著玩手機(jī)錯過了那個點(diǎn)的公交,那我只能等 10:20 這一趟了菇绵。
ScrollView滾動時timer不執(zhí)行的原因
這里有個概念叫 CommonModes
:一個 Mode
可以將自己標(biāo)記為Common
屬性(通過將其 ModeName
添加到 RunLoop
的 commonModes
中)肄渗。每當(dāng) RunLoop
的內(nèi)容發(fā)生變化時,RunLoop
都會自動將 _commonModeItems
里的 Source/Observer/Timer
同步到具有 Common
標(biāo)記的所有Mode里咬最。
應(yīng)用場景舉例:主線程的 RunLoop
里有兩個預(yù)置的 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
翎嫡。這兩個 Mode 都已經(jīng)被標(biāo)記為Common
屬性。DefaultMode
是 App 平時所處的狀態(tài)永乌,TrackingRunLoopMode
是追蹤 ScrollView 滑動時的狀態(tài)惑申。當(dāng)你創(chuàng)建一個 Timer 并加到 DefaultMode
時,Timer 會得到重復(fù)回調(diào)铆遭,但此時滑動一個TableView時硝桩,RunLoop 會將 mode 切換為 TrackingRunLoopMode
沿猜,這時 Timer 就不會被回調(diào)枚荣,并且也不會影響到滑動操作。
有時你需要一個 Timer啼肩,在兩個 Mode 中都能得到回調(diào)橄妆,一種辦法就是將這個 Timer 分別加入這兩個 Mode。還有一種方式祈坠,就是將 Timer 加入到頂層的 RunLoop 的 commonModeItems
中害碾。commonModeItems
被 RunLoop 自動更新到所有具有Common
屬性的 Mode 里去。
NSTimer導(dǎo)致內(nèi)存泄漏的原因及解決方法
原因
-
NSTimer
作為局部變量使用赦拘,在添加到RunLoop
中的時會被RunLoop
強(qiáng)引用慌随,導(dǎo)致Timer
本身不能被釋放。 - NSTimer作為屬性使用時只能聲明成強(qiáng)引用(使用弱引用添加到RunLoop中會導(dǎo)致奔潰)躺同,在創(chuàng)建
timer
的過程中阁猜,timer
會強(qiáng)引用當(dāng)前添加的target
,若target
是當(dāng)前timer
所屬的對象時蹋艺,會造成循環(huán)引用的問題 - 當(dāng)
timer
釋放時所在線程跟timer
添加到的RunLoop
對應(yīng)的線程不一致時釋放會有問題
解決方法
在合適的時機(jī)調(diào)用invalidate
方法剃袍。蘋果官方的描述如下:
This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.
invaliate
是唯一一個將timer
從NSRunLoop
對象移除的方法,如果我們是使用target
跟userinfo
對象創(chuàng)建的捎谨,那么同樣會移除對這些對象強(qiáng)引用民效。
#import "TimerViewController.h"
@interface TimerViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TimerViewController
- (void)dealloc {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
- (void)viewDidLoad {
[super viewDidLoad];
[self propertyTimer];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
if ([self.timer isValid]) {
[self.timer invalidate];
}
}
#pragma mark - private method
- (void)propertyTimer {
self.timer = [NSTimer timerWithTimeInterval:5
target:self
selector:@selector(propertyTimerMethod)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer
forMode:NSRunLoopCommonModes];
[self.timer fire];
}
- (void)propertyTimerMethod {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
- (void)localTimer {
//局部變量 無法釋放
NSTimer *timer = [NSTimer timerWithTimeInterval:5
target:self
selector:@selector(localTimerMethod)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSRunLoopCommonModes];
[timer fire];
}
- (void)localTimerMethod {
NSLog(@"%@", NSStringFromSelector(_cmd));
}