今天在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];