NSTimer循环引用

430 阅读2分钟

我们先看一下下面一段代码,如果您在项目中也出现如下使用,很不幸的告诉您,内存泄露了 我们准备三个控制器VC1,VC2,VC3,其中在VC2中开启一个定时器,当我们从VC1 push到VC2 VC2中定时器开启,打印timer run ,在从VC2 push到VC3定时器仍然正常,这符合开发时的 场景,VC2未销毁,定时器中任务正常进行,当我们从VC3 pop到VC2,再从VC2 pop到VC1, 期望是VC2销毁,定时器销毁,但是我们看到VC2中的dealloc函数并没有执行,导致timer没有被销毁,产生了内存泄露,我们分析原因不难看出 self -> timer -> self 导致循环引用 截屏2022-01-26 10.47.25.png

截屏2022-01-26 10.47.45.png

截屏2022-01-26 10.47.59.png

2022-01-26 11.04.38.gif

对于上面的处理,有些小伙伴会想到,将这个定时器放在生命周期函数中去销毁,比如

-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
}
//或者
-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear: animated];
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

但是这么处理,有些不符合业务实际场景,只要页面消失,定时器就会被销毁,而且比较麻烦 那怎么处理呢?只需要将上面 self -> timer -> self 这个环解开即可,请接着往下看 解决循环引用我们最先想到的是block中使用__weak

__weak typeof(self) weakSelf = self;
void(^block)(void) = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%@",strongSelf);
};
block();
//timer 使用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(timerRun) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode: NSRunLoopCommonModes];

这里我就不录gif了,测试后发现,从VC2 pop 到VC1的时候,定时器仍然无法销毁, 所以这种方式不可用,为什么呢?

timer 和 block 是不一样的 block具有捕获能力,会根据修饰词,传入什么类型,捕获成什么类型 但是timer不会,苹果文档明确说明 截屏2022-01-26 11.40.58.png

所以我们需要使用其他的方式,NSProxy,MrProxy如下,里面涉及到消息转发的内容,会在之后 的博客中补充消息转发

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MrProxy : NSProxy
-(instancetype)proxyWithObject:(id)object;
@end

NS_ASSUME_NONNULL_END

#import "MrProxy.h"

@interface MrProxy()
@property(nonatomic,weak)id object;
@end
@implementation MrProxy

-(instancetype)proxyWithObject:(id)object{
    self.object = object;
    return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return  self.object;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    MrProxy *proxy = [[MrProxy alloc]proxyWithObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(timerRun) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode: NSRunLoopCommonModes];
}
-(void)timerRun{
    NSLog(@"timer run");
}
-(void)dealloc{
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
    NSString *className =  [NSString stringWithUTF8String:class_getName([self class])];
    NSLog(@"=============== %@ ========= 释放",className);
}