由于计时器会保留其目标对象,使用计时器时很容易引起循环引用,如下代码所示:
@interface XXClass : NSObject
- (void)start;
- (void)stop;
@end
@implementation XXClass {
NSTimer *timer;
}
- (id)init {
return [super init];
}
- (void)dealloc {
[timer]
}
- (void)stop {
[timer invalidate];
timer = nil;
}
- (void)start {
timer = [NSTimerscheduledTimerWithTimeInterval:5.0
target:self
selector:selector(doSomething)
userInfo:nil
repeats:YES];
}
- (void)doSomething {
//doSomething
}
@end
大多数开发者可能都会这样来实现定时器。创建定时器的时候,由于目标对象是self,所以要保留此实例。然而,因为定时器是用实例变量存放的,所以实例也保留了定时器,这就造成了循环引用。除非调用stop方法,或者系统回收实例,才能打破循环引用,如果无法确保stop一定被调用,就极易造成内存泄露。
当指向XXClass实例的最后一个外部引用移走之后,该实例仍然会继续存活,因为定时器还保留着它。而定时器对象也不可能被系统释放,因为实例中还有一个强引用正在指向它。这种内存泄露是很严重的,如果定时器每次轮训都执行一些下载工作,常常会更容易导致其他内存泄露问题。
这个问题可以通过Block来解决:
@interface NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(xx_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)xx_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userinfo;
if(block) {
block();
}
}
@end
定时器现在的target是NSTimer类对象,这是个单例,此处依然有循环引用,然后类对象无需回收,所以不用担心。
这套代码并不能解决问题,例如:
- (void)start {
timer = [NSTimer xx_scheduledTimerWithTimeInterval:.5
block:^{
[self doSomething];
}
repeats:YES];
}
这段代码里还是有循环引用,因为Block捕获了self变量。此处只要改用weak引用,即可打破循环引用。
- (void)start {
__weak XXClass *weakSelf = self;
timer = [NSTimer xx_scheduledTimerWithTimeInterval:.5
block:^{
XXClass *strongSelf = weakSelf;
[strongSelf doSomething];
}
repeats:YES];
}
先定义了一个弱引用,令其指向self,然后使块捕获这个引用,而不直接去捕获普通的self变量。也就是说,self不会为计时器所保留。当块开始执行时,立刻生成strong引用,以保证实例在执行期间持续存活。
采用这种写法之后,如果外界指向XXClass实例的最后一个引用将其释放,则该实例就可为系统所回收了。