NSTimer,沒錯,定時器。我們開發中經常使用到的一個東西,而且我們在使用它的時候差不多都是按照以下代碼來使用的:
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerAction:(NSTimer *)timer
{
NSLog(@"%@", timer);
}
but,如果你將timer所屬的控制器推出后,發現timer此時還在執行,這是因為timer的執行需要依賴于runloop,在timer創建好放入runloop之后,并且如果timer是循環執行的,如果不顯示調用invalidate方法,那么timer是停不下來的。
同時,如果此時你重寫了這個控制器的dealloc方法,并且讓這個控制器pop出navigation所管理的棧時(我的這個控制器是由navigation所push出來的),你會發現dealloc方法并不會執行,這表明控制器并沒有被釋放,這是為什么呢,這是因為NSTimer在添加target時,會對這個target進行retain。所以就會造成上面這種情況:控制器要釋放,就要釋放它的所有實例變量,當釋放到timer時,timer要釋放他所持有的target,而此時的target是該控制器,所以造成了循環引用,從而造成了內存泄露。
要避免這種情況,我能想到的一個辦法就是在設定timer的target時,將target-action保存,target改設置為另一個和timer不存在引用關系的變量,進而避免泄露。
代碼如下:
首先定義一個用來保存target-action的對象
@interface PltTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;
@end
@implementation PltTimerTarget
- (void)pltTimerTargetAction:(NSTimer *)timer
{
if (self.target) {
//該方法會在RunLoop為DefaultMode時才會調用,與timer的CommonMode沖突
//[self.target performSelector:self.selector withObject:timer afterDelay:0.0];
//該方法可以正常在CommonMode中調用,但是會報警告
//[self.target performSelector:self.selector withObject:timer];
//最終方法
IMP imp = [self.target methodForSelector:self.selector];
void (*func)(id, SEL, NSTimer*) = (void *)imp;
func(self.target, self.selector, timer);
} else {
[self.timer invalidate];
self.timer = nil;
}
}
@end
然后我們自己定義一個方法,用來設置timer(這里我定義的是默認循環的timer,這種比不循環的要常用)
+ (instancetype)pltScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo
{
PltTimerTarget *timerTarget = [[PltTimerTarget alloc] init];
timerTarget.target = aTarget;
timerTarget.selector = aSelector;
NSTimer *timer = [NSTimer timerWithTimeInterval:ti target:timerTarget selector:@selector(pltTimerTargetAction:) userInfo:userInfo repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
timerTarget.timer = timer;
return timerTarget.timer;
}
這樣,我們就能在代碼中正常使用timer了
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer pltScheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerAction:) userInfo:@"userInfo"];
//這樣創建的timer,target的dealloc方法不會執行,因為timer會持有target,進而造成循環引用
// self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerAction:) userInfo:@"userInfo" repeats:YES];
}
- (void)timerAction:(NSTimer *)timer
{
NSLog(@"%@", timer.userInfo);
}
- (void)dealloc
{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%@ dealloc", self);
}
此時,dealloc方法是會執行的,并且能順利的將timer從runloop中停止,避免了內存泄露和資源浪費。
Demo地址