美文网首页
[定时器]使用NSTimer定时器出现内存泄漏问题

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

作者: Levi段玉磊 | 来源:发表于2018-06-15 00:49 被阅读0次

    有没有发现在使用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是如何实现定时器方法的?

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/lktpeftx.html