美文网首页
NSTimer之前我真不懂

NSTimer之前我真不懂

作者: 喝酸奶舔下盖 | 来源:发表于2018-09-17 17:48 被阅读0次

    NSTimer不就是定时器吗,这个平时经常用的,

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                self->startTimer = [NSTimer scheduledTimerWithTimeInterval:self.time target:self selector:@selector(start:) userInfo:nil repeats:YES];
                [[NSRunLoop currentRunLoop] run];
            });
    

    这样写之后不做其他处理的同学断点下dealloc方法,你会情不自禁的说三句“我擦、我擦、我擦”。(😄)

    NSTimer导致的内存泄漏

    少年郎之前一直都是这样用NSTimer,在排查一个页面内存泄漏的时候发现VC pop之后dealloc死活不走,该弱化的对象已经弱化,该处理的方法已经处理,即便是所有成员变量的引用计数也都一一排查确定正常之后dealloc方法还是不走,最后只能向一直认为没有问题的定时器下手,问题居然还真他妈的出现在这。向之前写的项目默哀半分钟,之前用错你了😢。
    查过资料了解到,为了保证参数的生命周期,NSTimer会对target对象retain一次,做强引用。以保证即便target销毁了,定时器还能正常调用timeEvent。因为定时器要加到RunLoop中,所以RunLoop强引用着NSTimer,一般情况下你的target就是当前的控制器,如果你想让控制器如你所愿的销毁了,首先得销毁NSTimer。不然NSTimer强引用着self,self就无法销毁,从而导致内存泄漏。

    销毁定时器的方法

    [startTimer invalidate];
     startTimer = nil;
    

    timer被schedule的时候,timer会持有target对象,NSRunLoop对象会持有timer。当invalidate被调用时,NSRunLoop对象会释放对timer的持有,timer会释放对target的持有。除此之外,没有途径可以释放timer对target的持有。所以解决内存泄露就必须撤销timer,若不撤销,target对象将永远无法释放。

    销毁timer

    如果可以的话在viewWillDisappear方法里销毁timer,或者点击返回按钮的时候销毁timer,不过这些都是局限性。如果要再push一个VC,视图消失的时候销毁timer就会有问题,返回按钮的方法里销毁还要考虑左滑手势...,这些感觉都不太“科学”。
    看到有大佬把target特殊处理成不是当前控制器,timer就不再强引用self,dealloc方法自然就能正常调用。

    - (void)dealloc
    {
        [startTimer  invalidate];
        startTimer = nil;
    }
    

    具体实现代码

    #import <Foundation/Foundation.h>
    
    typedef void (^WXWTimerBlock)(id userInfo);
    
    @interface WXWTimer : NSObject
    
    + (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          target:(id)aTarget
                                        selector:(SEL)aSelector
                                        userInfo:(id)userInfo
                                         repeats:(BOOL)repeats;
    
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(WXWTimerBlock)block
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats;
    
    
    @end
    
    
    @interface WXWTimerTarget : NSObject
    
    @property (nonatomic, weak) id target;
    @property (nonatomic, assign) SEL selector;
    @property (nonatomic, weak) NSTimer* timer;
    
    @end
    
    @implementation WXWTimerTarget
    
    - (void)timeAction:(NSTimer *)timer {
        if(self.target) {
            
            [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
            
        } else {
            [self.timer invalidate];
        }
    }
    
    @end
    
    
    @implementation WXWTimer
    
    + (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          target:(id)aTarget
                                        selector:(SEL)aSelector
                                        userInfo:(id)userInfo
                                         repeats:(BOOL)repeats {
        WXWTimerTarget* timerTarget = [[WXWTimerTarget alloc] init];
        timerTarget.target = aTarget;
        timerTarget.selector = aSelector;
        timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                             target:timerTarget
                                                           selector:@selector(timeAction:)
                                                           userInfo:userInfo
                                                            repeats:repeats];
        return timerTarget.timer;
    }
    
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(WXWTimerBlock)block
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats {
        NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
        if (userInfo != nil) {
            [userInfoArray addObject:userInfo];
        }
        return [self scheduledTimerWithTimeInterval:interval
                                             target:self
                                           selector:@selector(timerBlock:)
                                           userInfo:[userInfoArray copy]
                                            repeats:repeats];
    }
    
    + (void)timerBlock:(NSArray*)userInfo {
        WXWTimerBlock block = userInfo[0];
        id info = nil;
        if (userInfo.count == 2) {
            info = userInfo[1];
        }
        
        if (block) {
            block(info);
        }
    }
    
    
    @end
    

    NSTimer与RunLoop的关系

    之前写了个类似淘宝的消息滚动控件。本来也没有注意到这个问题,换工作面试的时候面试官看到这个问我怎么实现的,直接回答说:“用NSTimer加个定时器,设置重复滚动就可以了”。面试官笑了笑没说什么。场面一度十分尴尬。回来之后查了下才知道人家想要的回答应该是下面这样的,而不是我认为的那样😂。

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

    苹果加入RunLoop的默认的模式是NSDefaultRunLoopMode,当你滑动scrollView的时候runloop将会切换到UITrackingRunLoopMode、在加入的时候设置NSRunLoopCommonModes模式。这样所有模式都可以工作了。

    非主线程中创建定时器需要让定时器执行

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          
          NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];
           [[NSRunLoop currentRunLoop] run];
           
       });
    

    RunLoop用来循环处理响应事件,每个线程都有一个RunLoop,苹果不允许自己创建RunLoop, 而且只有主线程的RunLoop是默认打开的,其他线程的RunLoop如果需要使用就必须手动打开。scheduledTimerWithTimeInterval这个方法创建好NSTimer以后会自动将它添加到当前线程的RunLoop,非主线程中只有调用run方法定时器才能开始工作

    相关文章

      网友评论

          本文标题:NSTimer之前我真不懂

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