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地址