iOS 中的 timer 任务(寻找内存恶鬼之旅)

前言

     在 iOS 的开发过程中定时任务中能找到使用的场景,然而在 iOS 中默认的有关 timerapi 总是那么晦涩难用,而且暗坑不断,一旦遇上,会让你一脸懵逼,为了不再同一个地方跌倒两次,我决心花些时间做一篇总结,也用以提醒读者,谨慎使用。      之前在做一个空白页的计时器的时候使用到了 CADisplayLink,这货把我坑惨了, 循环引用导致内存随着时间的增加而上升,短时间使用没啥感觉,要不是使用工具这是很难发现的。

分析

      通常,在解决循环引用的时候我们会引入 weak , 通过 weak 修饰打破循环引用中的 , 如:

    @property (nonatomic, weak) CADisplayLink *link;

    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(fireAction)];
    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
复制代码

然而,这样做 link 直接不工作了, 因为 link 没有别的地方引用,当它初始化完成立即就被释放掉了 。那么换一种思路呢?

    __weak typeof(self) weakSelf = self;
    self.link = [CADisplayLink displayLinkWithTarget:weakSelf selector:@selector(fireAction)];
    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
复制代码

这样做也是徒劳的,当 self.link 持有 weakSelf 时也就是持有了 self, 而 link 是通过 target 强持有的 self 所以还是无法打破形成的环,我们通过 Memory Graph 就可以检测是否内存图关系:

这是 RunLoopTimer 的内存关系,再看看 Timertarget 的关系:

方案

      既然这个环用常规的方法无法打破,那该怎么办呢? 这时候 NSProxy 就可也发挥它的长处了。我们实现一个 NSProxy 的子类 WeakProxyWeakProxy 弱引用一个 target ,然后在通过 WeakProxy 消息转发到 target 从而达到破除循环的效果:

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
复制代码

proxy 弱引用的 target 所以不影响 target 的正常释放,当 target 释放后,link 引用计数减一 link 释放,proxy 引用计数减一也会释放,因此,原来的环不在了,完美解决了相互引用的问题。

源码 Demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值