RxSwift-中介者模式(Timer)
2019-08-12 本文已影响0人
May_Dobin
中介者
顾名思义就是一个桥梁,通过中介者
使对象间解耦。
首先看一下定时器Timer
循环引用问题无法释放,下面的代码:
class ViewController: UIViewController {
var mTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
mTimer = Timer.init(timeInterval: 1, target: proxy, selector: #selector(timerFire), userInfo: nil, repeats: true)
RunLoop.current.add(mTimer!, forMode: .common)
}
@objc func timerFire(){
print("timer fire")
}
deinit {
print("\(self) 走了")
}
}
上面的代码,在我们让ViewController
“销毁”后,实际由于mTimer
对self
的强引用,Timer
会继续执行,而ViewController
并没有销毁,那么我们如何解决呢?
很庆幸,在iOS 10
之后苹果爸爸引入了新的方法,通过下面的方法初始化计时器,就不会产生循环引用:
mTimer = Timer.init(timeInterval: 1, repeats: true, block: { (timer) in
print("timer fire \(timer)")
})
RunLoop.current.add(mTimer!, forMode: .common)
那么问题来了,iOS 10
之前的系统,如何解决接下来,我们引入中介者
类——MyProxy
。在这个类中有两种处理方式:
第一种
MyProxy
使用self
作为target
,#selector(timerIsGo)
作为selector
初始化计时器,在timerIsGo
中做容错处理
class MyProxy: NSObject {
weak var target : NSObjectProtocol?
var sel: Selector?
var timer: Timer?
override init() {
super.init()
}
func my_scheduledTimer(timeInterval time: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats aRepeats: Bool){
self.timer = Timer(timeInterval: time, target: self as Any, selector: #selector(timerIsGo), userInfo: userInfo, repeats: aRepeats)
RunLoop.current.add(self.timer!, forMode: .common)
self.target = aTarget as? NSObjectProtocol
self.sel = aSelector
}
@objc fileprivate func timerIsGo(){
if self.target != nil {
//此处如果做了如下判断,外界调用时
//如果是通过这种形式传入方法 let selector = NSSelectorFromString("timerFire")
//如果漏写了方法实现,很难定位timer 不响应的问题
if self.target?.responds(to: self.sel) == true {
self.target!.perform(self.sel)
}else{
print("\(self.target!) 中没有实现\(self.sel!)")
self.timer?.invalidate()
self.timer = nil
}
}else{
self.timer?.invalidate()
self.timer = nil
}
}
deinit {
print("\(self) 走了")
}
外界调用如下:
let selector = NSSelectorFromString("timerFire")
myProxy.my_scheduledTimer(timeInterval: 1, target: self, selector: selector, userInfo: nil, repeats: true)
特别注意:
- 现在的引用链为
self
->proxy
-weak
->target
->self
,并没有产生强循环引用 - 在
MyProxy
中初始化计时器时用的target
是MyProxy
,而不是传入的aTarget
- 通过
self.target
保存原类ViewController
,self.sel
保存原来的timerFire
方法 - 通过
MyProxy
中的timer
不断调用timerIsGo()
,间接响应原类self.target
中的self.sel
方法 - 在
timerIsGo ()
中判断target
是否存在,不存在就销毁计时器
第二种
MyProxy
依然使用self
作为target
,使用传进来的aSelector
作为selector
初始化计时器,通过runtime
进行方法交换,响应为自定义的方法timerIsGo()
,在消息转发时做容错处理;详见代码:
func my_scheduledTimer(timeInterval time: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats aRepeats: Bool){
self.timer = Timer(timeInterval: time, target: self, selector: aSelector, userInfo: userInfo, repeats: aRepeats)
RunLoop.current.add(self.timer!, forMode: .common)
self.target = aTarget as? NSObjectProtocol
self.sel = aSelector
guard self.target?.responds(to: self.sel) == true else{
return
}
let method = class_getInstanceMethod(self.classForCoder, #selector(timerIsGo))
class_replaceMethod(self.classForCoder, self.sel!, method_getImplementation(method!), method_getTypeEncoding(method!))
}
@objc fileprivate func timerIsGo(){
if self.target != nil {
self.target!.perform(self.sel)
}else{
self.timer?.invalidate()
self.timer = nil
}
}
deinit {
print("\(self) 走了")
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if self.target?.responds(to: self.sel) == true {
return self.target
}
else{
print("在此处理异常")
return super.forwardingTarget(for: aSelector)
}
}
代码分析:
guard self.target?.responds(to: self.sel) == true else{
return
}
let method = class_getInstanceMethod(self.classForCoder, #selector(timerIsGo))
class_replaceMethod(self.classForCoder, self.sel!, method_getImplementation(method!), method_getTypeEncoding(method!))
- 如果外界没有实现
selector
方法,直接return
,下面的class_replaceMethod
方法交换就不会执行 - 找不到方法实现,计时器就不会有响应,然后通过
runtime
的消息查找机制,最终会调用forwardingTarget
进行消息转发:
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if self.target?.responds(to: self.sel) == true {
return self.target
}
else{
print("在此处理异常")
return super.forwardingTarget(for: aSelector)
}
}
我们直接抛出异常,当然如果想要工程更加完美,可以在此手动添加方法实现,做出响应处理。