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];