先看代碼
class timerTestVC: UIViewController {
private var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = .white
// 創(chuàng)建了個定時器
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerRuns), userInfo: nil, repeats: true)
}
@objc private func timerRuns(){
debugPrint("-----run------")
}
deinit {
debugPrint("----deinit-----")
}
}
分析
當我們點擊返回按鈕時舶赔,發(fā)現(xiàn) deinit 并未執(zhí)行谦秧,而且定時還在跑。
那么我們就可以知道锥累,在這個controller
中還有對象未配釋放調(diào)石咬,造成內(nèi)存泄漏。
我們來分析timer
:
這里我們定義了timer
對象
private var timer: Timer!
并且我們使用它在當前controller
中創(chuàng)建的個定時器删性,那么我們可以知道焕窝,controller
中持有timer
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerRuns), userInfo: nil, repeats: true)
又因為在Timer
中有個target
中持有個self
也就是當前的controller
target: self
還有timer在創(chuàng)建的時候需要將其加入到RunLoop
中,主線程中的RunLoop
是常駐內(nèi)存同時對Timer的強引用
所以我們可以知道巴帮,這個controller
中出現(xiàn)了循環(huán)引用的問題榕茧。
解決方案
一客给、 用block
方式創(chuàng)建timer
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { time in
debugPrint("-----run------")
})
二、使用消息轉(zhuǎn)發(fā)機制 self
用一個中間類代替
1蜻拨、使用 NSObject
方式
創(chuàng)建一個類TimerProxy
集成自NSObject
private weak var target: AnyObject!
init(target: AnyObject){
self.target = target
super.init()
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return self.target
}
那么在調(diào)用的時候這樣:target: TimerProxy(target: self)
timer = Timer.scheduledTimer(timeInterval: 1, target: TimerProxy(target: self), selector: #selector(timerRuns), userInfo: nil, repeats: true)
2、使用NSProxy
的方式
但其在 Swift
中會有兩個報錯的問題
1收夸、init無法重載血崭,這個倒不是很要緊。
2序苏、NSInvocation不可用捷凄,這就很關鍵了,直接導致了forwardInvocation方法無法重載
NSInvocation' is unavailable in Swift: NSInvocation and related APIs not available
因此我們放棄直接使用 NSProxy
的方法匈睁。但我們能否根據(jù)NSProxy
的原理來自己實現(xiàn)一個Proxy
呢桶错?
NSProxy
是一個消息重定向的一個抽象類院刁,類似一個中間代理人,通過消息轉(zhuǎn)發(fā)的機制將消息轉(zhuǎn)發(fā)到另一個實例
在OC
中重寫以下兩個方法即可
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
那么我們是否可以通過方法替換的方式來達到目的呢退腥?
將實例中的Selector
傳入進來
required init(target: NSObjectProtocol, sel: Selector)
用這個來獲取一個自定義的方法
class_getInstanceMethod(self.classForCoder, #selector(targetMethod))
用此方法將sel
替換成targetMethod
class_replaceMethod(self.classForCoder, sel, method_getImplementation(tagMethod!), method_getTypeEncoding(tagMethod!))
class TimerProxy2: NSObject {
public weak var target: NSObjectProtocol?
public var sel:Selector?
required init(target: NSObjectProtocol, sel: Selector) {
super.init()
self.target = target
self.sel = sel
let tagMethod = class_getInstanceMethod(self.classForCoder, #selector(targetMethod))
class_replaceMethod(self.classForCoder, sel, method_getImplementation(tagMethod!), method_getTypeEncoding(tagMethod!))
}
@objc private func targetMethod(){
if self.target != nil {
self.target?.perform(self.sel)
}
}
}
三狡刘、使用GCD的方式創(chuàng)建個定時器
var total = 60
let timer = DispatchSource.makeTimerSource()
timer.schedule(wallDeadline: DispatchWallTime.now(), repeating: DispatchTimeInterval.seconds(1), leeway: DispatchTimeInterval.milliseconds(0))
timer.setEventHandler {
if total <= 0{
timer.cancel()
DispatchQueue.main.async {
}
}else{
DispatchQueue.main.async {
total -= 1
}
}
}
timer.resume()