我们先看一下下面一段代码,如果您在项目中也出现如下使用,很不幸的告诉您,内存泄露了
我们准备三个控制器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 导致循环引用
对于上面的处理,有些小伙伴会想到,将这个定时器放在生命周期函数中去销毁,比如
-(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不会,苹果文档明确说明
所以我们需要使用其他的方式,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);
}