iOS开发笔记之六十三——一个NSTimer引发内存泄漏

******阅读完此文,大概需要5分钟******

一、问题产生与分析

先看下产生的代码:

- (void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"dealloc!!!!!!!");
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0f
                                                  target:self
                                                selector:@selector(timerFire)
                                                userInfo:nil
                                                 repeats:YES];
    [self.timer fire];
}

- (void)timerFire
{
    NSLog(@"fire");
}

在这段代码,你在dealloc处放置一个breakpoint,你会发现dealloc方法不会执行的。因为此时存在着一个引用循环:



每个NSTimer其实是被添加在所在线程的runloop中,而runloop对timer是一种强持有关系,看下苹果官网:


也就是说,此时的timer采取strong property的方式其实是不合理的。那么为什么Runloop要strong reference to a timer呢,首先,NSTimer的执行需要加到runloop中去。RunLoop有一个CFRunLoopTimerRef 是基于时间的触发器的类,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调(这就是为什么要强持有target)。

二、解决方法

1、及时invalidate()掉Timer

invalidate()的作用,苹果有描述 :


执行之后,Runloop对Timer的强引用就会remove掉,同时timer对target的强引用也会remove掉,通过CFGetRetainCount()方法查看self的引用即可知道,验证如下


NO Repeats的Timer是会自动invalidate()的,所以,invalidate()后timer仅仅是self的一个属性变量了。

对于Repeats类型的Timer需要在合适的时机去手动invalidate()了,例如viewDidDisappear方法中,就是一种不错的尝试。

2、不需要手动设置NSTimer为invalid 的方法:造一个假的target给NSTimer,假的target作用就是用于接受NSTimer的强引用。

具体的思路代码,如下:

[...]
@implementation NSWeakTimerTarget
{
    __weak target;
    SEL selector;
}
[...]
 
- (void)timerDidFire:(NSTimer *)timer
{
    if (target)
    {
        [target performeSelector:selector withObject:timer];
    }
    else
    {
        [timer invalidate];
    }
}
@end
 
@implementation NSWeakTimer
    + (NSTimer *)scheduledTimerWithTimerInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeat:(BOOL)repeats
{
    NSWeakTimerTarget *timerTarget = [[NSWeakTimerTarget alloc] init]
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:timerTarget selector:@selector(timerDidFire:) userInfo: userInfo repeats: repeats];
    return timerTarget.timer;
}
@end
此法真正的target并不会被timer 持有,当真正的target为空的时候,timer会执行invalid。此法的github地址:https://github.com/ChatGame/HWWeakTimer

三、参考资料

1、https://developer.apple.com/reference/foundation/timer/1415405-invalidate?changes=latest_minor

2、http://www.jianshu.com/p/f9999b5958f8



猜你喜欢

转载自blog.csdn.net/lizitao/article/details/56835434