相信iOS開(kāi)發(fā)過(guò)程中,肯定大多數(shù)人都知道Timer的釋放不掉問(wèn)題,但是否認(rèn)真考慮過(guò)其中釋放不掉的原因?
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerRun:) userInfo:nil repeats:YES];
NSTimer不釋放原因
其中考慮較多的一個(gè)版本是:controller與Timer循環(huán)引用,導(dǎo)致彼此不能釋放,但真正原因真是這樣嗎?
驗(yàn)證方式:
1.把timer寫(xiě)為局部變量,避免controller強(qiáng)引用timer
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerRun:) userInfo:nil repeats:YES];
然而,并不能釋放。
2.我們來(lái)看看引用情況
打開(kāi)xcode的Debug Memory Graph工具,在這里

Debug Memory Graph
能看到內(nèi)存引用情況

內(nèi)存引用情況
驗(yàn)證結(jié)果:只有timer單向的指向target,target并未指向timer。而timer的不釋放原因在于runloop的強(qiáng)引用,見(jiàn)官方文檔:

image.png
錯(cuò)誤解決辦法
1.dealloc中調(diào)用invalidate
- (void)dealloc {
if(_timer) {
[_timer invalidate];
}
}
這是不行的!
如果_timer的target是self,會(huì)對(duì)self進(jìn)行強(qiáng)引用(即使傳入weakSelf也是不行的),導(dǎo)致self不能釋放,也就不會(huì)走到dealloc方法里。
2.viewWillDisappear中invalidate
這種方式是可以釋放掉的。
但如果我只是想在離開(kāi)此頁(yè)時(shí)要釋放,進(jìn)入下一頁(yè)時(shí)不要釋放,場(chǎng)景就不適用了。
正確解決辦法
添加一個(gè)NSTimer類的擴(kuò)展,把target指給[NSTimer class],事件由加方法接收,然后把事件通過(guò)block傳遞出來(lái)。
@interface NSTimer (block)
+ (instancetype)repeatWithInterval:(NSTimeInterval)interval block:(void(^)(NSTimer *timer))block;
@end
@implementation NSTimer (block)
+ (instancetype)repeatWithInterval:(NSTimeInterval)interval block:(void(^)(NSTimer *timer))block {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(trigger:) userInfo:[block copy] repeats:YES];
return timer;
}
+ (void)trigger:(NSTimer *)timer {
void(^block)(NSTimer *timer) = [timer userInfo];
if (block) {
block(timer);
}
}
@end
巧妙點(diǎn)在于把block作為timer的userInfo傳遞進(jìn)入trigger:方法,避免了在本類中再添加個(gè)參數(shù)記錄block。
使用示例
@interface CAAnimationViewController ()
@property (nonatomic, weak) NSTimer *timer;
@end
@implementation CAAnimationViewController
- (void)viewDidLoad {
kWeakSelf
self.timer = [NSTimer repeatWithInterval:1 block:^(NSTimer *timer) {
//收到timer回調(diào)
[weakSelf dosomething];
}];
}
- (void)dealloc {
[_timer invalidate];
}
@end