前言
在 iOS
的開發過程中定時任務中能找到使用的場景,然而在 iOS
中預設的有關 timer
的 api
總是那麼晦澀難用,而且暗坑不斷,一旦遇上,會讓你一臉懵逼,為了不再同一個地方跌倒兩次,我決心花些時間做一篇總結,也用以提醒讀者,謹慎使用。
之前在做一個空白頁的計時器的時候使用到了 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
就可以檢測是否記憶體圖關係:
RunLoop
與 Timer
的記憶體關係,再看看 Timer
與 target
的關係:
方案
既然這個環用常規的方法無法打破,那該怎麼辦呢? 這時候 NSProxy
就可也發揮它的長處了。我們實現一個 NSProxy
的子類 WeakProxy
,WeakProxy
弱引用一個 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
引用計數減一也會釋放,因此,原來的環不在了,完美解決了相互引用的問題。