首页 > 系统 > iOS > 正文

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

2019-11-08 00:05:21
字体:
来源:转载
供稿:网友

一、问题产生与分析

先看下产生的代码:

- (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


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表