目標(biāo)環(huán)境:Swift 4.0
有效的代碼
/// 處理timer強(qiáng)引用類
public class HCWeakTimerProxy: NSObject {
weak var target:NSObjectProtocol?
var sel:Selector?
/// required,實(shí)例化timer之后需要將timer賦值給proxy答捕,否則就算target釋放了猾封,timer本身依然會(huì)繼續(xù)運(yùn)行
public weak var timer:Timer?
public required init(target:NSObjectProtocol?, sel:Selector?) {
self.target = target
self.sel = sel
super.init()
// 加強(qiáng)安全保護(hù)
guard target?.responds(to: sel) == true else {
return
}
// 將target的selector替換為redirectionMethod,該方法會(huì)重新處理事件
let method = class_getInstanceMethod(self.classForCoder, #selector(HCWeakTimerProxy.redirectionMethod))!
class_replaceMethod(self.classForCoder, sel!, method_getImplementation(method), method_getTypeEncoding(method))
}
@objc func redirectionMethod () {
// 如果target未被釋放噪珊,則調(diào)用target方法晌缘,否則釋放timer
if self.target != nil {
self.target!.perform(self.sel)
} else {
self.timer?.invalidate()
print("HCWeakProxy: invalidate timer.")
}
}
}
使用方式
let proxy = HCWeakTimerProxy.init(target: self, sel: #selector(autoScroll))
self.timer = Timer.scheduledTimer(timeInterval: self.scrollTimerInterval, target: proxy, selector: #selector(autoScroll), userInfo: nil, repeats: true)
proxy.timer = self.timer
如果有需要看解決思路的就往下看齐莲。
解決思路
Timer如果不使用invalidate方法釋放的話,就會(huì)造成循環(huán)引用導(dǎo)致target無法釋放的問題磷箕。
一開始的解決思路是繼承NSProxy选酗,但出現(xiàn)了2個(gè)問題只好放棄:
- init無法重載,這個(gè)倒不是很要緊岳枷。
- NSInvocation不可用芒填,這就很關(guān)鍵了,直接導(dǎo)致了forwardInvocation方法無法重載空繁;
NSInvocation' is unavailable in Swift: NSInvocation and related APIs not available
根據(jù)NSProxy的實(shí)現(xiàn)原理制作了Proxy類
/// 此類無法解決殿衰,僅做思路參考
class Proxy: NSObject {
weak var target:NSObjectProtocol?
public init(_ target:NSObjectProtocol?) {
self.target = target
super.init()
}
override func responds(to aSelector: Selector!) -> Bool {
return self.target?.responds(to:aSelector) == true
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if self.target?.responds(to: aSelector) == true {
return self.target
} else {
return super.forwardingTarget(for: aSelector)
}
}
}
// 具體調(diào)用
let proxy = Proxy.init(self)
let timer = Timer.scheduledTimer(timeInterval: 2, target: proxy, selector: #selector(autoScroll), userInfo: nil, repeats: true)
運(yùn)行后發(fā)現(xiàn)幾個(gè)問題:
- responds方法不會(huì)觸發(fā),因此這個(gè)方法就不需要重寫了盛泡;
- 在不調(diào)用timer.invalidate的情況下成功銷毀了target闷祥,但是timer還在;
- 由于timer還存在因此會(huì)繼續(xù)觸發(fā)forwardingTarget方法傲诵,并且進(jìn)入了else分支凯砍,由于Proxy類中本身并無selector所指定方法,崩潰了拴竹,返回nil也一樣會(huì)崩潰悟衩。
因此要先解決兩個(gè)問題:
- target銷毀后,timer必須跟著銷毀
- 由于target銷毀后栓拜,forwardingTarget依然會(huì)觸發(fā)座泳,因此需要在Proxy類中動(dòng)態(tài)注入selector方法。
第一個(gè)問題好解決幕与,只要在Proxy中弱引用timer挑势,并在else分支銷毀timer就可以了。
第二個(gè)問題本來想在resolveInstanceMethod中動(dòng)態(tài)注入方法纽门,但發(fā)現(xiàn)這個(gè)方法中無法調(diào)用到target薛耻,即便使用self.forwardingTarget方法返回也是nil值
override class func resolveInstanceMethod(_ sel: Selector!) -> Bool {}
因此考慮在init方法中注入方法,Proxy類改為:
class Proxy: NSObject {
weak var target:NSObject?
public weak var timer:Timer?
var sel:Selector?
public init(target:NSObject?, sel:Selector?) {
self.target = target
super.init()
let method = class_getInstanceMethod(target as? AnyClass, sel!)
class_addMethod(self.classForCoder, sel!, method_getImplementation(method!), method_getTypeEncoding(method!))
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if self.target?.responds(to: aSelector) == true {
return self.target
} else {
self.timer?.invalidate()
return self
}
}
}
但是發(fā)現(xiàn)獲得的method是nil赏陵,崩潰饼齿。
最后思考,當(dāng)timer調(diào)用selector的時(shí)候蝙搔,是否可以重定向到Proxy中的另一個(gè)方法中缕溉,由那個(gè)方法來執(zhí)行forwardingTarget的工作。這樣就算target銷毀了吃型,timer調(diào)用selector的時(shí)候也不會(huì)崩潰证鸥。最后得到的類如下:
/// 處理timer強(qiáng)引用類
public class HCWeakTimerProxy: NSObject {
weak var target:NSObjectProtocol?
var sel:Selector?
/// required,實(shí)例化timer之后需要將timer賦值給proxy,否則就算target釋放了枉层,timer本身依然會(huì)繼續(xù)運(yùn)行
public weak var timer:Timer?
public required init(target:NSObjectProtocol?, sel:Selector?) {
self.target = target
self.sel = sel
super.init()
// 加強(qiáng)安全保護(hù)
guard target?.responds(to: sel) == true else {
return
}
// 將target的selector替換為redirectionMethod泉褐,該方法會(huì)重新處理事件
let method = class_getInstanceMethod(self.classForCoder, #selector(HCWeakTimerProxy.redirectionMethod))!
class_replaceMethod(self.classForCoder, sel!, method_getImplementation(method), method_getTypeEncoding(method))
}
@objc func redirectionMethod () {
// 如果target未被釋放,則調(diào)用target方法鸟蜡,否則釋放timer
if self.target != nil {
self.target!.perform(self.sel)
} else {
self.timer?.invalidate()
print("HCWeakProxy: invalidate timer.")
}
}
}
使用方式:
let proxy = HCWeakTimerProxy.init(target: self, sel: #selector(autoScroll))
self.timer = Timer.scheduledTimer(timeInterval: 3, target: proxy, selector: #selector(autoScroll), userInfo: nil, repeats: true)
proxy.timer = self.timer
最后為了方便使用膜赃,不用每次都考慮proxy,寫了一個(gè)extension:
public extension Timer {
public class func hc_scheduledTimer(timeInterval ti: TimeInterval, target aTarget: NSObjectProtocol, selector aSelector: Selector, userInfo aInfo: Any?, repeats yesOrNo: Bool) -> Timer {
let proxy = HCWeakTimerProxy.init(target: aTarget, sel: aSelector)
let timer = Timer.scheduledTimer(timeInterval: ti, target: proxy, selector: aSelector, userInfo:aInfo, repeats: yesOrNo)
proxy.timer = timer
return timer
}
}
// 外部調(diào)用target直接傳self就行
self.timer = Timer.hc_scheduledTimer(timeInterval: 3, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)