NSTimer,CADisplayLink内存泄漏

今天在Q群 问了些面试题,有一个 NSTimer 怎么处理内存泄漏的问题,

  • 就是NSTimer的target被强引用了,而通常target就是所在的控制器,他又强引用的timer,造成了循环引用
    比如 平时我们一般在ViewController 添加NSTimer
    在ViewController的dealloc 方法里进行释放
- (void)dealloc{
      [_timer invalidate];
      _timer=nil;
    NSLog(@"释放%s",__func__);
}

但是deallco方法根本不会执行,除非我们在ViewController 添加事件 提前进行 [_timer invalidate]; ViewController才会执行dealloc, 当我们想让Timer一直运行直到ViewController被dealloc的时候才被释放,这就不行了。

  • 解决方案:
    在阅读YYKit的源码的时候 发现ibireme大神的 YYWeakProxy 类处理方案非常巧妙,NSTimer,CADisplayLink 都适用,使用NSProxy解决NSTimer内存泄漏问题,
    原理:
    就是生成一个临时对象弱引用回调方,以此破解强引用环。重写YYWeakProxy类的消息转发方法,保证接收方是实际回调的对象,没有形成循环引用

YYWeakProxy

@property (nonatomic, weak, readonly) id target;

+ (instancetype)proxyWithTarget:(id)target {
    return [[YYWeakProxy alloc] initWithTarget:target];
}
//将消息接收对象改为 _target
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}
//self 对 target 是弱引用,一旦 target 被释放将调用下面两个方法,如果不实现的话会 crash
- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

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

YYWeakProxy 继承自 NSProxy,是 Foundation 框架两大基类之一,实现了 NSObject 协议。
NSProxy 做为消息转发的抽象代理类,没有 init 方法,子类必须实现 initWithXXX: forwardInvocation: 和 methodSignatureForSelector: 方法)。
当不能识别方法时候,就会调用forwardingTargetForSelector方法,在这个方法中,我们可以将不能识别的传递给其它对象处理
需要重载methodSignatureForSelector和forwardInvocation的,为什么呢?因为_target是弱引用的,所以当_target可能释放了,当它被释放了的情况下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation没实现的话,就直接crash了!!!
这也是为什么这两个方法中是随便写的 ,而没有将消息转发给其他对象的操作

  • 使用的时候是这样的
//NSTimer,
 _timer = [NSTimer timerWithTimeInterval:1.0
                                              target:[YYWeakProxy proxyWithTarget:self]
                                            selector:@selector(timerClick:)
                                            userInfo:nil
                                             repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_longPressTimer forMode:NSRunLoopCommonModes];

//CADisplayLink
   _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(linkClick:)];
    [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

参考:
IOS定时器操作和NSTimer的各种坑
NSTimer和实现弱引用的timer的方式

推荐阅读更多精彩内容