[定时器]使用NSTimer定时器出现内存泄漏问题

有没有发现在使用NSTimer类的定时器,实现定时器的类并没有走dealloc方法?这是为什么?

NSTimer常用定时器写法

正常我们利用NSTimer方式实现代码如下:

@property (nonatomic, strong) NSTimer *tanMukuTimer;

- (void)stopTanMuku
{
    [_tanMukuTimer invalidate];
    _tanMukuTimer = nil;
}

- (void)beginTanMuKu
{
    if (_tanMukuTimer == nil) {
        _tanMukuTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(requestNetwork) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:_tanMukuTimer forMode:NSDefaultRunLoopMode];
    }
}

问题

用此方法创建的定时器会在指定的间隔时间执行定时器的方法-requestNetwork,知道开发者稍后将其手动关闭为止,target与selector参数标识计时器将在哪个对象上调用哪个方法,计时器会保留目标对象,等到自身失效,才会释放目标对象,因为计时器是用实例变量来存放的,所以实例也保留了计时器,然而就出现了保留环。除非在某个时间点手动调用-stopTanMuku 方法停止计时器,从而打破保留环,但通过调用方法来避免内存哦泄漏,手动干预的方式并不理想。如果想在系统回收本类时调用停止计时器的方案,打破循环引用,就会出现问题,因为计时器对象有引用计数,所以self实例的保留计数不会为0,因此系统就不会调用dealloc方法,而现在有没有人调用停止计时器的方法,所以就会出现内存泄漏,类永远无法被释放掉。如果是计时器调用的是请求数据方法,那就导致更多其他严重的内存泄漏问题。

解决方法

这种方法无法通过代码检测出来,因此我们要对这个问题进行避免,可以通过block进行解决,因此要写个NSTimer的分类:

@interface NSTimer (WPGBlockSupport)

+ (NSTimer *)wpg_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(void(^)(void))block
                                        repeats:(BOOL)repeats;

@end

#import "NSTimer+WPGBlockSupport.h"

@implementation NSTimer (WPGBlockSupport)

+ (NSTimer *)wpg_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(void(^)(void))block
                                        repeats:(BOOL)repeats
{
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(wpg_blockInvoke:) userInfo:[block copy] repeats:repeats];
}

+ (void)wpg_blockInvoke:(NSTimer *)timer
{
    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

@end

将block作为userinfo的参数传进去,只要计时器还有效,就会一直保留着它,传入参数时要通过copy方法将block,copy到堆上,否则等到稍后要执行它的时候,该块可能已经无效了。计时器现在的target是NSTimer的类对象,由于是个单例,所以不用担心产生循环引用的问题。但是这本身还不能解决问题,因为block捕获了self变量,所以block要保留实例,计时器有通过userinfo保留了block,最后实例本身还要保留计时器,不过改成weak引用,就直接打破保留环,打破循环引用。

- (void)upstartQueryTimer
{
    [self upstopQueryTimer];
    if (nil == _upqueryNoticeTimer) {
        __weak typeof(self) weakSelf = self;
        _upqueryNoticeTimer = [NSTimer wpg_scheduledTimerWithTimeInterval:3.0 block:^{
            __strong typeof(self) strongself = weakSelf;
            [strongself startAnimition];
        } repeats:NO];
    }
}

这段代码先定义了弱引用,然后通过weak变量不直接捕获self变量,这样self就不会被计时器所保留,当块开始执行时,而不直接捕获普通的self变量,当开始执行时,立刻在block内生成strong引用,以保证实例执行期间可以存活,但由于是临时变量,strong引用的self并没有存在堆中,执行后自动就被释放了。这样就完美解决了,不会调用系统不会调用dealloc的内存泄漏问题。

思考与行动:

  1. 除了通过NSTimer实现计时器外,还有其他实现定时器的方式么?哪个更好用?
  2. 通过GCD实现的定时器会出现内存泄漏的问题吗?GCD是如何实现定时器方法的?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,655评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,148评论 1 32
  • OC语言基础 1.类与对象 类方法 OC的类方法只有2种:静态方法和实例方法两种 在OC中,只要方法声明在@int...
    奇异果好补阅读 4,358评论 0 11
  • 大家好,我是林暖汐。刚刚来到简书,还不太会用,求大家多多关照,多多指教~我的QQ是:2170029500.微...
    林暖汐_汐宝阅读 202评论 0 1
  • 身为一个口红控,我一直秉持着“口红是女人的春药”这个原则,保持着一月或者两月败一只口红的记录。在以前啊,我还是...
    阿欣阅读 317评论 0 0