强引用
环境准备
- 此时有两个界面A、B,从A push 到B界面,在B界面中有如下定时器代码。当从B界面
pop
回到A界面时,发现定时器没有停止,其方法仍然在执行。
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
- (void)dealloc
{
[self.timer timeInterval];
self.timer = nil;
}
- 其主要原因是
B界面
没有释放
,即没有
执行dealloc
方法,导致timer
也无法停止
和释放
解决方案一
- 重写
didMoveToParentViewController
方法
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 无论push 进来 还是 pop 出去 正常跑
// 就算继续push 到下一层 pop 回去还是继续
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
- 定义
timer
时,采用闭包
的形式,因此不
需要指定target
- (void)blockTimer{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer fire - %@",timer);
}];
}
解决方案二
探索释放不掉的原因
- 我们可以通过官方文档查看
timerWithTimeInterval:target:selector:userInfo:repeats:
方法中对target的描述
- 从文档中可以看出,
timer
对传入的target
具有强持有
,即timer
持有self
。由于timer
是定义在B
界面中,所以self
也
持有timer
,因此self -> timer -> self
构成了循环引用
。
在OC底层探索(二十三)Block用法及原理文章中,针对循环应用提供了几种解决方式。我们我们尝试通过__weak
即弱引用
来解决,代码修改如下
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
但是我们再次运行程序,进行push-pop
跳转。发现问题还是存在,即定时器
方法仍然在执行
,并没有执行B
的dealloc
方法,为什么呢?
- 我们使用
__weak
虽然打破了self -> timer -> self
之前的循环引用,即引用链
变成了self -> timer -> weakSelf -> self
。但是此时还有一个Runloop
对timer
的强持有
,因为Runloop
的生命周期
比B
界面更长
,所以导致了timer无法释放
,同时也导致了B
界面的self
也无法释放
。所以,最初引用链应该是这样的
加上weakSelf
之后,变成了这样
解决思路
一、
中介者
模式,即不使用self
,依赖于其他对象
在timer
模式中,我们重点关注的是fireHome
能执行,并不关心timer
捕获的target
是谁,由于这里不方便使用self
(因为会有强持有
问题),所以可以将target
换成其他对象
,例如将target
换成NSObject
对象,将fireHome交给target执行
- 将
timer
的target
由self改成objc
//**********1、定义其他对象**********
@property (nonatomic, strong) id target;
//**********1、修改target**********
self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];
//**********3、imp**********
void fireHomeObjc(id obj){
NSLog(@"%s -- %@",__func__,obj);
}
强烈推荐使用!!! 二、利用
NSProxy
虚基类的子类
- 首先定义一个继承自
NSProxy
的子类
//************NSProxy子类************
@interface QProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface QProxy()
@property (nonatomic, weak) id object;
@end
@implementation QProxy
+ (instancetype)proxyWithTransformObject:(id)object{
QProxy *proxy = [QProxy alloc];
proxy.object = object;
return proxy;
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
- 将
timer
中的target
传入NSProxy
子类对象,即timer
持有NSProxy子类对象
//************解决timer强持有问题************
self.proxy = [QProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
//在dealloc中将timer正常释放
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
这样做的主要目的是将强引用的注意力转移成了消息转发
。虚基类
只负责消息转发
,即使用NSProxy
作为中间代理
、中间者
这里有个疑问,定义的proxy
对象,在dealloc释放时,还存在吗?
-
proxy
对象会正常释放
,因为vc
正常释放了,所以可以释放其持有者,即timer
和proxy
,timer
的释放也打破
了runLoop
对proxy
的强
持有。完美的达到了两层释放
,即vc -×-> proxy <-×- runloop
,解释如下:-
vc释放,然后使了
proxy
的释放 -
dealloc
方法中,timer
进行了释放
,所以runloop
强引用也释放
了
-