預防 Timer 的循環引用
2017.03.02 02:42* 字數 584 閱讀 1098評論 0喜歡 1
在iOS開發過程中,Timer(NSTimer)是我們經常要使用的一個類。通過Timer,可以定時觸發某個事件,或者執行一些特定的操作。但是稍微不注意,就會導致內存泄漏(memory leak),而這種內存泄漏,就是循環引用引起的。例如在一個視圖控制器MyViewController
fileprivate var myTimer: Timer?
self.myTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(myTimerAction), userInfo: nil, repeats: true)
那么你調用profile的Leaks工具時會發現MyViewController退出之后,就會檢測到內存泄漏。如果你看Apple的開發文檔足夠細心,你將會發現問題所在:
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
原來Timer調用scheduledTimer時,會強引用target,這里即是MyViewController對象。解決方法就是按照文檔所說在某個地方或時間點,手動將定時器invalidated就可以了。
self.myTimer.invalidate()
self.myTimer = nil
但是你千萬不要將上述代碼放到deinit里面(慣性思維會讓我們把釋放清除工作放到deinit里),因為循環引用之后MyViewController對象不會釋放,deinit將永遠不會被調用。你可以重載viewDidDisappear,放到里面去。或者確定不需要定時器時,及時銷毀定時器。
雖然問題得到解決,但很明顯,啰嗦且不夠優雅。所幸iOS 10.0+之后,一切變得簡單起來了……
weak var weakSelf = self
Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block:{(timer: Timer) -> Void in
weakSelf?.doSomething()
})
項目往往需要向下兼容,有沒有辦法使得iOS 10.0之前版本能夠這樣簡單的使用 block,優雅的解決循環飲用呢?答案是肯定的。
首先創建模版類保存 block:
class Block {
let f : T
init(_ f: T) { self.f = f }
}
Timer增加如下擴展
extension Timer {
class func app_scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Swift.Void) -> Timer {
if #available(iOS 10.0, *) {
return Timer.scheduledTimer(withTimeInterval: interval, repeats: repeats, block: block)
}
return Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(app_timerAction), userInfo: Block(block), repeats: repeats)
}
class func app_timerAction(_ sender: Timer) {
if let block = sender.userInfo as? Block<(Timer) -> Swift.Void> {
block.f(sender)
}
}
}
這樣就沒有了iOS版本的限制,方便快捷的使用Timer了:
weak var weakSelf = self
Timer.app_scheduledTimer(withTimeInterval: interval, repeats: true, block:{(timer: Timer) -> Void in
weakSelf?.doSomething()
})
總結:
1、當調用Apple的API時,需要傳遞類對象self本身的,我們一定要看清文檔,self會不會被保留強引用(MAC時代的被retain);
2、當self被強引用時,像Timer一樣,增加類似的一個擴展,或者可以很好的解決問題;
3、Block模版類,或許可以很優雅的解決你所遇到的問題。