目标环境:Swift 4.0
有效的代码
/// 处理timer强引用类
public class HCWeakTimerProxy: NSObject {
weak var target:NSObjectProtocol?
var sel:Selector?
/// required,实例化timer之后需要将timer赋值给proxy,否则就算target释放了,timer本身依然会继续运行
public weak var timer:Timer?
public required init(target:NSObjectProtocol?, sel:Selector?) {
self.target = target
self.sel = sel
super.init()
// 加强安全保护
guard target?.responds(to: sel) == true else {
return
}
// 将target的selector替换为redirectionMethod,该方法会重新处理事件
let method = class_getInstanceMethod(self.classForCoder, #selector(HCWeakTimerProxy.redirectionMethod))!
class_replaceMethod(self.classForCoder, sel!, method_getImplementation(method), method_getTypeEncoding(method))
}
@objc func redirectionMethod () {
// 如果target未被释放,则调用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方法释放的话,就会造成循环引用导致target无法释放的问题。
一开始的解决思路是继承NSProxy,但出现了2个问题只好放弃:
- init无法重载,这个倒不是很要紧。
- NSInvocation不可用,这就很关键了,直接导致了forwardInvocation方法无法重载;
NSInvocation' is unavailable in Swift: NSInvocation and related APIs not available
根据NSProxy的实现原理制作了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)
}
}
}
// 具体调用
let proxy = Proxy.init(self)
let timer = Timer.scheduledTimer(timeInterval: 2, target: proxy, selector: #selector(autoScroll), userInfo: nil, repeats: true)
运行后发现几个问题:
- responds方法不会触发,因此这个方法就不需要重写了;
- 在不调用timer.invalidate的情况下成功销毁了target,但是timer还在;
- 由于timer还存在因此会继续触发forwardingTarget方法,并且进入了else分支,由于Proxy类中本身并无selector所指定方法,崩溃了,返回nil也一样会崩溃。
因此要先解决两个问题:
- target销毁后,timer必须跟着销毁
- 由于target销毁后,forwardingTarget依然会触发,因此需要在Proxy类中动态注入selector方法。
第一个问题好解决,只要在Proxy中弱引用timer,并在else分支销毁timer就可以了。
第二个问题本来想在resolveInstanceMethod中动态注入方法,但发现这个方法中无法调用到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
}
}
}
但是发现获得的method是nil,崩溃。
最后思考,当timer调用selector的时候,是否可以重定向到Proxy中的另一个方法中,由那个方法来执行forwardingTarget的工作。这样就算target销毁了,timer调用selector的时候也不会崩溃。最后得到的类如下:
/// 处理timer强引用类
public class HCWeakTimerProxy: NSObject {
weak var target:NSObjectProtocol?
var sel:Selector?
/// required,实例化timer之后需要将timer赋值给proxy,否则就算target释放了,timer本身依然会继续运行
public weak var timer:Timer?
public required init(target:NSObjectProtocol?, sel:Selector?) {
self.target = target
self.sel = sel
super.init()
// 加强安全保护
guard target?.responds(to: sel) == true else {
return
}
// 将target的selector替换为redirectionMethod,该方法会重新处理事件
let method = class_getInstanceMethod(self.classForCoder, #selector(HCWeakTimerProxy.redirectionMethod))!
class_replaceMethod(self.classForCoder, sel!, method_getImplementation(method), method_getTypeEncoding(method))
}
@objc func redirectionMethod () {
// 如果target未被释放,则调用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,写了一个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
}
}
// 外部调用target直接传self就行
self.timer = Timer.hc_scheduledTimer(timeInterval: 3, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)