iOS - 关于NSTimer的循环引用

作者: LinXunFeng | 来源:发表于2016-12-16 17:07 被阅读298次

    现象

    在当前控制器(ViewController)的view上添加了一个自定义的view(LXFTimerView),
    LXFTimerView在成功创建出来后添加了定时器NSTimer并加入RunLoop开始工作,
    当在当前控制器里将LXFTimerView移除掉后,定时器还在工作,而且LXFTimerView里的dealloc并没有调用

    现象

    代码

    LXFTimerView.m

    #import "LXFTimerView.h"
    
    @interface LXFTimerView()
    /** 定时器 */
    @property(nonatomic, weak) NSTimer *timer;
    @end
    
    @implementation LXFTimerView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            [self addTimer];
        }
        return self;
    }
    
    - (void)dealloc {
        NSLog(@"LXFTimerView - dealloc");
        [self removeTimer];
    }
    
    #pragma mark - 定时器方法
    /** 添加定时器方法 */
    - (void)addTimer {
        // 创建定时器
        if (self.timer) { return; }
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(log) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    }
    /** 移除定时器 */
    - (void)removeTimer {
        [self.timer invalidate];
        self.timer = nil;
    }
    - (void)log {
        NSLog(@"定时器 -- %s", __func__);
    }
    @end
    

    ViewController.m

    #import "ViewController.h"
    #import "LXFTimerView.h"
    
    @interface ViewController ()
    /** timerView */
    @property(nonatomic, weak) LXFTimerView *timerView;
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        LXFTimerView *timerView = [[LXFTimerView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 200)];
        timerView.backgroundColor = [UIColor orangeColor];
        self.timerView = timerView;
        [self.view addSubview:timerView];   
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self.timerView removeFromSuperview];
    }
    @end
    

    引用关系

    引用关系

    问题就出在LXFTimerView与NSTimer之间,在创建定时器时执行

    [NSTimer scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:];
    

    会将LXFTimerView进行强引用,什么?我怎么知道?看下图


    NSTimer

    翻译:定时器保持着对target的强引用,直到定时器作废
    那为什么LXFTimerView中的timer属性要用weak?? 不用着急,下面�即将揭晓~

    解决方案

    让定时器指着另一个对象,让那个对象来执行LXFTimerView中需要执行的方法。
    引用关系如下图所示


    LXFWeakTarget

    创建一个继承于NSObject的类 LXFWeakTarget,并提供一个创建定时器的方法(苹果官方的方法,对scheduledTimerWithTimeInterval进行转到定义操作【就是command+左键】就可以得到)
    LXFWeakTarget.h

    #import <Foundation/Foundation.h>
    @interface LXFWeakTarget : NSObject
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    @end
    
    #import "LXFWeakTarget.h"
    
    @interface LXFWeakTarget()
    @property(nonatomic, weak) id target;
    @property(nonatomic, assign) SEL selector;
    @end
    
    @implementation LXFWeakTarget
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
        // 创建当前类的对象
        LXFWeakTarget *object = [[LXFWeakTarget alloc] init];
        object.target = aTarget;
        object.selector = aSelector;
    
        return [NSTimer scheduledTimerWithTimeInterval:ti target:object selector:@selector(execute:) userInfo:userInfo repeats:yesOrNo];
    }
    - (void)execute:(id)obj {
        [self.target performSelector:self.selector withObject:obj]; 
    }
    @end
    

    在LXFTimerView.m中导入LXFWeakTarget的头文件

    #import "LXFWeakTarget.h"
    

    将创建定时器的类改为 LXFWeakTarget

    self.timer = [LXFWeakTarget scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(log) userInfo:nil repeats:YES];
    

    现在再来执行一下程序


    �执行dealloc

    最后缕下思路

    • 我们用一个LXFWeakTarget来替LXFTimerView执行一些操作。
    • 当没有被定时器强引用的LXFTimerView从父控件上被移除时,就会执行dealloc方法,LXFTimerView被销毁。
    • 将定时器作废并设为nil,这样定时器对LXFWeakTarget的引用也没有了,LXFWeakTarget也会被销毁。

    好,那“为什么LXFTimerView中的timer属性要用weak”这个问题就不用多加解析了吧。

    相关文章

      网友评论

        本文标题:iOS - 关于NSTimer的循环引用

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