計(jì)時(shí)器要和“運(yùn)行循環(huán)”(run loop)相關(guān)聯(lián)备恤,運(yùn)行循環(huán)到時(shí)會(huì)觸發(fā)任務(wù)编整。創(chuàng)建NSTimer時(shí)找御,可以將其“預(yù)先安排”在當(dāng)前的運(yùn)行循環(huán)中元镀,也可以先創(chuàng)建好,然后由開發(fā)者來調(diào)度霎桅。無論采用哪種方式栖疑,只有把計(jì)時(shí)器放在運(yùn)行循環(huán)里,它才能正常觸發(fā)任務(wù)滔驶。
NSTimer常用的創(chuàng)建方法有以下兩種
-
第一種創(chuàng)建方式
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL) repeats;
-
scheduledTimerWithTimeInterval
在主線程創(chuàng)建的定時(shí)器會(huì)在創(chuàng)建后自動(dòng)將timer
添加到主線程的runloop
并啟動(dòng). - 主線程的
runloopMode
為NSDefaultRunLoopMode
遇革,但是在 ScrollView 滑動(dòng)時(shí)執(zhí)行的是UITrackingRunLoopMode
.NSDefaultRunLoopMode
被掛起,定時(shí)器失效揭糕,等到停止滑動(dòng)才恢復(fù). - 因此需要將
timer
分別加入UITrackingRunLoopMode
和NSDefaultRunLoopMode
中
或者直接添加到[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop mainRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];
NSRunLoopCommonModes
中[[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];
-
-
第二種創(chuàng)建方式
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL) repeats;
-
timerWithTimeInterval
創(chuàng)建的定時(shí)器不會(huì)直接啟動(dòng)萝快,而需要手動(dòng)添加到runloop
中;為防止出現(xiàn)滑動(dòng)視圖時(shí)定時(shí)器被掛起著角,可直接添加到NSRunLoopCommonModes
.
-
保留環(huán)問題
重復(fù)執(zhí)行模式的計(jì)時(shí)器揪漩,很容易引入保留環(huán)
@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end
@implementation EOCClass{
NSTimer *_poliTimer;
}
- (id) init{
return [super init];
}
- (void)dealloc{
[_poliTimer invalidate];
}
- (void)stopPolling{
[_poliTimer invalidate];
_poliTimer = nil;
}
- (void)startPolling{
_poliTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(p_doPoll) userInfo:nil repeats:YES];
}
- (void)p_doPoll{
// Poll the resource
}
如果創(chuàng)建了本類實(shí)例,并調(diào)用了startPolling方法吏口。創(chuàng)建計(jì)時(shí)器的時(shí)候奄容,由于目標(biāo)對(duì)象是self,所以要保留此實(shí)例产徊。然而昂勒,因?yàn)橛?jì)時(shí)器是用實(shí)例變量存放的,所以實(shí)例也保留了計(jì)數(shù)器舟铜,于是就產(chǎn)生了保留環(huán)戈盈。
調(diào)用stopPolling方法或令系統(tǒng)將實(shí)例回收(會(huì)自動(dòng)調(diào)用dealloc方法)可以使計(jì)時(shí)器失效,從而打破循環(huán)谆刨,但無法確保startPolling方法一定調(diào)用塘娶,而由于計(jì)時(shí)器保存著實(shí)例,實(shí)例永遠(yuǎn)不會(huì)被系統(tǒng)回收痴荐。當(dāng)EOCClass實(shí)例的最后一個(gè)外部引用移走之后血柳,實(shí)例仍然存活,而計(jì)時(shí)器對(duì)象也就不可能被系統(tǒng)回收生兆,除了計(jì)時(shí)器外沒有別的引用再指向這個(gè)實(shí)例难捌,實(shí)例就永遠(yuǎn)丟失了膝宁,造成內(nèi)存泄漏。
解決方案:采用塊為計(jì)時(shí)器添加新功能根吁,為NSTimer添加分類
@interface NSTimer (EOCBlocksSupport)
+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
@end
@implementation NSTimer( EOCBlocksSupport)
+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats{
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(eoc_blockInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)eoc_blockInvoke:(NSTimer*)timer{
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
在外部調(diào)用時(shí)范例
- (void)startPolling{
__weak EOCClass *weakSelf = self;
_poliTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{
EOCClass *strongSelf = weakSelf;
[strongSelf p_doPoll];
} repeats:YES];
}
這段代碼先定義了一個(gè)弱引用指向self员淫,然后用塊捕獲這個(gè)引用,這樣self就不會(huì)被計(jì)時(shí)器所保留击敌,當(dāng)塊開始執(zhí)行時(shí)介返,立刻生成strong引用,保證實(shí)例在執(zhí)行器繼續(xù)存活沃斤。
采用這種寫法之后圣蝎,如果外界指向 EOCClass 實(shí)例的最后一個(gè)引用將其釋放,則該實(shí)例就可為系統(tǒng)所回收了衡瓶∨枪回收過程中還會(huì)調(diào)用計(jì)時(shí)器的 invalidate 方法,這樣的話哮针,計(jì)時(shí)器就不會(huì)再執(zhí)行任務(wù)了关面。此處使用 weak 引用還能令程序更加安全,因?yàn)橛袝r(shí)開發(fā)者可能在編寫 dealloc 時(shí)忘了調(diào)用計(jì)時(shí)器的 invalidate 方法十厢,從而導(dǎo)致計(jì)時(shí)器再次運(yùn)行等太,若發(fā)生此類情況,則塊里的 weakSelf 會(huì)變成 nil蛮放。
要點(diǎn)
NSTimer對(duì)象會(huì)保留其目標(biāo)缩抡,直到計(jì)時(shí)器本身失效為止,調(diào)用invalidate方法可令計(jì)時(shí)器失效筛武,另外缝其,一次性的計(jì)時(shí)器在觸發(fā)任務(wù)之后也會(huì)失效挎塌。
反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器徘六,很容易引入保環(huán),如果這種計(jì)時(shí)器的目標(biāo)對(duì)象又保留了計(jì)時(shí)器本身榴都,那肯定會(huì)導(dǎo)致保留環(huán)待锈。這種環(huán)狀保留關(guān)系,可能是直接發(fā)生的嘴高,也可能是通過其他對(duì)象間接發(fā)生的竿音。
可以擴(kuò)充NSTimer的功能,用“塊”來打破保留環(huán)拴驮。不過春瞬,除非NSTimer將來在公共接口里提供此功能,否則必須創(chuàng)建分類套啤,將相關(guān)實(shí)現(xiàn)代碼加入其中宽气。