• [crash详解与防护] NSTimer crash


    前言:

      NSTimer会保留其目标对象,如果不加以注意,就会持有保留环,造成内存泄露。

    一、 NSTimer保留环介绍

      Foundation框架中的NSTimer类,提供了在某个时间执行指定方法的功能,原型如下:

    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

      target和selector参数表示计时器将在哪个对象上调用哪个方法。repeats参数可以指定计时器是一次性的计时器还是重复模式的计时器。一次性的计时器在执行完相关任务之后就会失效。重复模式的计时器,必须手动调用invalidate方法,才能令其停止。

      重复执行的计时器很容易产生保留环而不能释放,如下所示:

    //  SLVTimerTestViewController.m
    @implementation SLVTimerTestViewController {
        NSTimer *_aTimer;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
         _aTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(timer_invoke) userInfo:nil repeats:YES];
    }
    -(void)dealloc {
        [_aTimer invalidate];
        NSLog(@"the timer vc dealloced!");
    }
    
    -(void)timer_invoke {
        NSLog(@"timer invoke!");
    }

    我们发现,当SLVTimerTestViewController不再展示的时候,并不会调用dealloc的方法,也就是SLVTimerTestViewController一直都不会释放。

    原因是,NSTimer在使用scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:方法时会保留其目标对象self。而_aTimer又是self的变量,self也保留了_aTimer,这样就形成了保留环。而Timer没有主动置为失效的情况下,Timer一直会保留self,所以即使SLVTimerTestViewController已经没有其它对象使用了,也还是有Timer在持有他,一直不会释放,也一直不会调用dealloc。

    二、NSTimer打破保留环的解决方案

      如下代码所示,可以使用block来打破保留环:可以定义一个弱引用,令其指向self,然后使块捕获这个引用,而不直接去捕获普通的self变量。也就是说,self不会为计时器所保留。当块开始执行时,立刻生成strong引用,以保证实例在执行期间持续存活。

    //  SLVTimerTestViewController.m
    @implementation SLVTimerTestViewController {
        NSTimer *_aTimer;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
         __weak SLVTimerTestViewController *weakSelf = self;
            _aTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 repeats:YES block:^(NSTimer* timer){
                  SLVTimerTestViewController *strongSelf = weakSelf;
                  [strongSelf timer_invoke];
            }];
    }
    -(void)dealloc {
        [_aTimer invalidate];
        NSLog(@"the timer vc dealloced!");
    }
    
    -(void)timer_invoke {
        NSLog(@"timer invoke!");
    }

    这样,我们就发现当没有其它对象使用SLVTimerTestViewController时,dealloc方法就会如期执行。这是在ios10之后苹果添加的新的方法,这个block的实现。在ios10之前自己可以用分类的方法实现如下:

    //  NSTimer+SLVBlocksSupport.m
    @implementation NSTimer (SLVBlocksSupport)
    +(NSTimer *)slv_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                             block:(void(^)())block
                                           repeats:(BOOL)repeats {
        return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(slv_blockInvoke:) userInfo:[block copy] repeats:repeats];
    }
    
    -(void)slv_blockInvoke:(NSTimer *)timer {
        void (^block)() = timer.userInfo;
        if(block){
            block();
        }
    }
    @end
  • 相关阅读:
    加载中动画
    跑步动画
    关键帧动画
    animate.css
    怪异盒子
    弹性项目属性
    改变元素大小
    Linux 文件系统 --磁盘I/O
    Linux 文件系统
    Sample Test Strategy
  • 原文地址:https://www.cnblogs.com/Xylophone/p/6394076.html
Copyright © 2020-2023  润新知