用Block解决NSTimer循环引用

由于计时器会保留其目标对象,使用计时器时很容易引起循环引用,如下代码所示:

@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实例的最后一个引用将其释放,则该实例就可为系统所回收了。

你可能感兴趣的:(用Block解决NSTimer循环引用)