問(wèn)題
很多人都知道, Timer
會(huì)通過(guò) Runloop
對(duì) target
進(jìn)行強(qiáng)引用, 需要手動(dòng)對(duì) Timer
進(jìn)行 invalidate
以便釋放強(qiáng)引用.
所以很多人就寫(xiě)了下面的代碼
...
override func viewDidLoad() {
super.viewDidLoad()
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
}
deinit {
self.timer.invalidate()
}
@objc func tick() {
print("tick")
}
這樣寫(xiě), 很快就會(huì)發(fā)現(xiàn)問(wèn)題, 因?yàn)槟愕?ViewController
在彈出之后, 依然在打印 tick
簡(jiǎn)單分析一下, 因?yàn)?Timer
對(duì) target
有一個(gè)強(qiáng)引用, 導(dǎo)致無(wú)法釋放, 我們代碼中對(duì) Timer 的釋放函數(shù)卻在析構(gòu)函數(shù)中, 而析構(gòu)函數(shù)根本就無(wú)法被調(diào)用, 也就造成了引用無(wú)法釋放, Timer
也無(wú)法結(jié)束.
錯(cuò)誤的解決方案:
看到引用無(wú)法釋放, 你可能會(huì)想到使用弱引用解決, 于是寫(xiě)出了下面的代碼
override func viewDidLoad() {
super.viewDidLoad()
weak var weakself = self
self.timer = Timer.scheduledTimer(timeInterval: 1, target: weakself, selector: #selector(tick), userInfo: nil, repeats: true)
}
...
結(jié)果是令人失望的, 即使這樣, 控制器也無(wú)法被釋放. 強(qiáng)引用的是變量指向的對(duì)象, 而不是變量本身, 弱引用并不能解決這個(gè)問(wèn)題.
正確的姿勢(shì)
既然無(wú)法直接解決, 那我們可以通過(guò)一個(gè)局部變量作為 target
, 然后在局部變量中存放一個(gè) weakself
來(lái)解決引用問(wèn)題.
先來(lái)定義一個(gè)類(lèi)型用來(lái)存放 weakself
和接收 Timer
事件
class TimerTarget: NSObject {
weak var target: AnyObject?
let timerSelctor: Selector
init(target: AnyObject, selector: Selector) {
self.timerSelctor = selector
super.init()
self.target = target
}
@objc func timerTick() {
_ = self.target?.perform(self.timerSelctor)
}
}
這里面通過(guò)一個(gè) weak
屬性指向了控制器, 這樣, 即使 Timer
對(duì) TimerTarget
增加一個(gè)強(qiáng)引用, 也不會(huì)對(duì)控制器的引用計(jì)數(shù)產(chǎn)生影響
override func viewDidLoad() {
super.viewDidLoad()
self.timer = Timer.scheduledTimer(timeInterval: 1, target: TimerTarget(target: self, selector: #selector(tick)), selector: #selector(TimerTarget.timerTick), userInfo: nil, repeats: true)
}
這樣操作后, 控制器就可以正常釋放了. 而且, 由于控制器會(huì)在釋放時(shí), 主動(dòng)釋放 Timer
, 這樣, TimerTarget
也可以正常釋放
進(jìn)階, 更簡(jiǎn)潔的解決方案
上面的代碼看起來(lái)略顯累贅, 有沒(méi)有跟簡(jiǎn)單的辦法?
當(dāng)然有, 使用 NSProxy
類(lèi), 將 Timer 觸發(fā)的方法轉(zhuǎn)發(fā)到我們的控制器中.
不過(guò)遺憾的是, Swift 由于不支持 NSInvocation
以及構(gòu)造函數(shù)無(wú)法構(gòu)造(NSProxy
沒(méi)有 init
方法), 只能使用混編來(lái)解決.
我們來(lái)創(chuàng)建一個(gè) OC 的類(lèi)
WeakTarget.h
文件
#import <Foundation/Foundation.h>
@interface WeakTarget : NSProxy
@property(weak, nonatomic) id target;
- (instancetype)initWithTarget:(id)target;
@end
WeakTarget.m
文件
#import "WeakTarget.h"
@implementation WeakTarget
- (instancetype)initWithTarget:(id)target{
self.target = target;
return self;
}
-(void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
@end
另外, 沒(méi)有用過(guò)混編的同學(xué)注意, 在詢問(wèn)是否需要?jiǎng)?chuàng)建bridge header 的時(shí)候, 一定要選擇創(chuàng)建, 否則就需要自己創(chuàng)建了.
在 XXX-Bridging-Header.h
中添加對(duì)這個(gè)頭文件的引用.
#import "WeakTarget.h"
這樣準(zhǔn)備工作就好了.
使用方式如下
self.timer = Timer.scheduledTimer(timeInterval: 1, target: WeakTarget(target: self), selector: #selector(tick), userInfo: nil, repeats: true)
本質(zhì)上還是一樣的, 都是在WeakTarget 里面存放一個(gè)弱引用的對(duì)象, 避免對(duì)控制器進(jìn)行強(qiáng)引用.
使用NSProxy 可以將方法重定向到控制器中, 代碼更加整潔.