OC底层探索(二十五)内存管理 --强引用

OC底层文章汇总

强引用

环境准备

  • 此时有两个界面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跳转。发现问题还是存在,即定时器方法仍然在执行,并没有执行Bdealloc方法,为什么呢?

  • 我们使用__weak虽然打破了 self -> timer -> self之前的循环引用,即引用链变成了self -> timer -> weakSelf -> self。但是此时还有一个Runlooptimer强持有,因为Runloop生命周期B界面更长,所以导致了timer无法释放,同时也导致了B界面的self无法释放。所以,最初引用链应该是这样的

在这里插入图片描述
加上weakSelf之后,变成了这样
在这里插入图片描述

解决思路

一、中介者模式,即不使用self,依赖于其他对象

timer模式中,我们重点关注的是fireHome能执行,并不关心timer捕获的target是谁,由于这里不方便使用self(因为会有强持有问题),所以可以将target换成其他对象,例如将target换成NSObject对象,将fireHome交给target执行

  • timertarget 由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正常释放了,所以可以释放其持有者,即timerproxytimer的释放也打破runLoopproxy持有。完美的达到了两层释放,即vc -×-> proxy <-×- runloop,解释如下:

    • vc释放,然后使了proxy的释放

    • dealloc方法中,timer进行了释放,所以runloop强引用也释放

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值