美文网首页
iOS - NSTimer中 Target 的 Self 强引用

iOS - NSTimer中 Target 的 Self 强引用

作者: 一滴矿泉水 | 来源:发表于2022-05-27 17:32 被阅读0次

    一、 问题原因

    • 当控制器ViewController跳转进入控制器OneiewController中的时候开启定时器,让定时器每隔一段时间打印一次,当OneiewController dismiss的时候,控制器并没有被销毁.然而定时器的timer invalidate 在dealloc中已经写了.

    • 如果没有定时器,则OneiewController可以正常销毁.

    • 原因在于下图:循环引用
      由于NSTimer 与 控制器的相互强引用 dealloc要等控制器销毁的时候才会调用,而NSTimer还没有invalidate,那么控制器一直被强引用,所以并不会走dealloc,结果不走dealloc就不会invalidate... (循环引用就出现了)

    截屏2022-05-27 下午4.41.20.png
    • 控制器ViewController跳转进入OneiewController中开启定时器
    #import "OneiewController.h"
    
    @interface OneiewController ()
    @property (nonatomic, strong) NSTimer *timer;
    
    @end
    
    @implementation OneiewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    -(void)viewDidLoad{
    
        [super viewDidLoad];
        //开启定时器
        self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(testTimerDealloc) userInfo:nil repeats:YES];
    
    }
    /** 方法一直执行 */
    -(void)testTimerDealloc{
    
        NSLog(@"-----");
    }
    /** 开启定时器以后控制器不能被销毁,此方法不会被调用 */
    -(void)dealloc{
         NSLog(@"xiaohui");
        [self.timer invalidate];
    
    }
    @end
    
    • 当开启定时器以后,testTimerDealloc方法一直执行,即使dismiss此控制器以后,也是一直在打印,而且dealloc方法不会执行.循环引用造成了内存泄露,控制器不会被释放.

    二、 解决办法

    • 由于循环引用的起因是target,则可以包装一个target,让target是另一个对象,而不是OneiewController即可.
    • 创建一个集成NSObject的分类TimerWeakTarget,创建类方法---开启定时器的方法
    @interface TimerWeakTarget : NSObject
    
    @property (nonatomic, assign) SEL selector;
    
    @property (nonatomic, weak) NSTimer *timer;
    
    @property (nonatomic, weak) id target;
    /**
     1.重写开启定时器方法,在内部对target进行替换,换成本类(TimerWeakTarget)的对象即可
     2.不会造成循环引用了,原控制器OneViewController属性有timer对timer强应用,timer内部对self强引用,但是self在此方法内部被替换成了本类的对象(TimerWeakTarget *),而本类的对象不会对OneViewController强引用,则不会造成循环引用,也就不会造成内存泄露
     */
    + (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats;
    
    @end
    
    • TimerWeakTarget.m文件中
    • 在下面我们封装的类的方法中,我们将开启定时器的方法 [NSTimer scheduledTimerWithTimeInterval:interval target:timer selector:@selector(fire:) userInfo:userInfo repeats:repeats];中的target换掉了,换成了 本类的对象,timer.在OneViewController中开启定时器的时候直接调用这个类方法,就不会造成循环引用
    #import "TimerWeakTarget.h"
    
    @implementation TimerWeakTarget
    + (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats{
    
        TimerWeakTarget * timer = [TimerWeakTarget new];
        timer.target = aTarget;
        timer.selector = aSelector;
    //-------------------------------------------------------------此处的target已经被换掉了不是原来的VIewController而是TimerWeakTarget类的对象timer
    timer.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:timer selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    return timer.timer;
    }
    
    -(void)fire:(NSTimer *)timer{
    
        if (self.target) {
            [self.target performSelector:self.selector withObject:timer.userInfo];
        
        } else {
    
            [self.timer invalidate];
        }
    }
    
    
    @end
    
    • 重新创建 调用 NSTimer
    #import "OneiewController.h"
    #import "TimerWeakTarget.h"
    
    @interface OneiewController ()
    @property (nonatomic, strong) NSTimer *timer;
    
    @end
    
    @implementation OneiewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    -(void)viewDidLoad{
    
        [super viewDidLoad];
        //开启定时器
        self.timer = [TimerWeakTarget scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(testTimerDealloc) userInfo:nil repeats:YES];
    
    }
    /** 方法一直执行 */
    -(void)testTimerDealloc{
    
        NSLog(@"-----");
    }
    /** 开启定时器以后控制器不能被销毁,此方法不会被调用 */
    -(void)dealloc{
        NSLog(@"xiaohui");
        [self.timer invalidate];
    
    }
    
    @end
    
    • 控制器dismiss以后可以正常被销毁.问题解决.

    文章持续更新中、希望对各位有所帮助、有问题可留言 大家共同学习.

    相关文章

      网友评论

          本文标题:iOS - NSTimer中 Target 的 Self 强引用

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