定义: 用一个中介对象(中介者)来封装一些列的对象交互,中介者使各对象不须要显示地相互引用,从而使其耦合松散,并且能够独立地改变他们之间的交互。swift
从上面 中介者模式 的定义彷佛知道了中介者的做用,可是具体如何使用呢?那么下面我将和小伙伴们一块儿来实现一个中介者。bash
在项目开发中,不少时候都会用到定时器。咱们可能会写出以下代码:ide
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timeFire), userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: .common)
}
@objc func timeFire() {
print("time fire")
}
deinit {
timer?.invalidate()
timer = nil
print("控制器销毁了")
}
}
复制代码
执行代码,定时器开始走了。可是当咱们离开控制器时,就会发现,deinit
并无执行。由于咱们给 Timer
的 target
传入的是 self
,因此会形成循环引用。函数
要想解决这个问题,咱们可使用block方式初始化 Timer,以下:oop
timer = Timer.init(timeInterval: 1, repeats: true
, block: { (timer) in
print("timer fire")
})
复制代码
这样就解决了循环引用的问题,可是咱们会发现block方式初始化 Timer 这个方法是在 iOS10 之后才出现的,因此在低于 iOS10 的版本上是没法使用的,即便没有这样的问题,可是每次都要重写 deinit
这个方法来中止定时器也是一个不够优雅的方式。测试
是否能够有一个其余东西来帮我解决定时器形成的循环引用以及中止定时器呢?这就是咱们即将介绍的 中介者模式。ui
既然是 中介者模式,那么必然会有一个 中介者, 因此咱们新建一个继承自 NSObject 的类 BOProxy 来充当 中介者。spa
class BOProxy: NSObject {
weak var target: NSObjectProtocol?
fileprivate var sel: Selector?
fileprivate var timer: Timer?
override init() {
super.init()
}
}
复制代码
由于中介者也须要知道外界的信息,因此须要保存响应者 target
,又为了打破循环引用,因此使用 weak 修饰符。使用 sel
保存 timer 调用的方法。使用 timer
保存内部初始化的定时器。code
咱们再增长一个 sechduleTimer
函数来初始化定时器,接收外界传入的参数。orm
func sechduleTimer(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) {
timer = Timer.init(timeInterval: ti, target: aTarget, selector: aSelector, userInfo: userInfo, repeats: yesOrNo)
self.target = aTarget as? NSObjectProtocol
self.sel = aSelector
guard target?.responds(to: sel) == true else {
return
}
RunLoop.current.add(timer!, forMode: .common)
}
复制代码
同时修改 controller 中的代码:
proxy.sechduleTimer(timeInterval: 1, target: self, selector: #selector(timeFire), userInfo: nil, repeats: true)
复制代码
而后执行,定时器可以响应。可是,退成控制器后,定时器并无被销毁啊。并且循环引用仍然存在,是中介者没有起做用吗?不是的。
咱们 sechduleTimer
中初始化 Timer
的时候,直接使用传入 target
来初始化 Timer
,因此循环引用并无被打破,中介者在其中仅充当封装者的做用,并无起到其应有的做用。
因此,咱们应该使用 BOProxy 自身,来初始化 Timer:
timer = Timer.init(timeInterval: ti, target: self, selector: aSelector, userInfo: userInfo, repeats: yesOrNo)
复制代码
让 BOProxy来响应定时器的回调,可是又会出现一个问题,BOProxy 并无实现定时器回调的 selector
啊。为了让 BOProxy 拥有外面的 seletor
,咱们使用 runtime 作方法交换。
...
RunLoop.current.add(timer!, forMode: .common)
let method = class_getInstanceMethod(self.classForCoder, #selector(boTimeFire))!
class_replaceMethod(self.classForCoder, self.sel!, method_getImplementation(method), method_getTypeEncoding(method))
复制代码
最终,定时器就会回调 BOProxy.boTimeFire 函数。咱们在 boTimeFire
函数中再来让 target
调用 selector
。
@objc fileprivate func boTimeFire() {
if self.target != nil {
self.target!.perform(self.sel)
} else {
self.timer?.invalidate()
self.timer = nil
}
}
复制代码
同时,还判断外界的 target
是否还存在,若是不存在则销毁定时器。完成定时器的自动销毁。
执行上面的代码,定时器完美响应,退出控制器,控制器也销毁了。
若是是初级开发者,作到这一步,已经能够了。但毕竟我是比初级开发者高一点点的初级开发者,那么我可能在传入 BOProxy selector
的时候,这样写:
let sel = NSSelectorFromString("timeFireNoIMP")
proxy.sechduleTimer(timeInterval: 1, target: self, selector: sel, userInfo: nil, repeats: true)
复制代码
可是呢,timeFireNoIMP
这个方法并无被实现,那么再运行会怎么样呢?咱们会发现,没有报错,可是定时器也没有响应。
这是由于咱们在 sechduleTimer
中 判断了 target?.responds(to: sel)
必需要 target
实现了 sel
才把 Timer 加入到RunLoop中。可是这样并很差,由于你找不到错误的缘由,不容易定位错误。
咱们能够在判断到 target
未实现 sel
时,打印一个提示。
guard target?.responds(to: sel) == true else {
print("\(sel!) 方法未实现")
return
}
复制代码
可是,若是项目中log太多的话,可能并很差发现这个提示。那么还有另外一种方式,我一直认为,若是有bug,在开发/测试阶段最好是能直接暴露出来,而不是等APP发布以后才被发现。没有比崩溃更能暴露bug的了。
因此,咱们直接将 timer
加入RunLoop,不作 selector
是否实现的判断。那么,在 selector
未被实现的状况下,必然会致使APP崩溃,可是在崩溃以前由于iOS的容错机制,其必然会进入消息转发阶段。咱们就在消息转发中来作错误提示,便于定位bug所在。
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if self.target?.responds(to: sel) == true {
return self.target
} else {
print("\(sel!) 方法未实现")
return super.forwardingTarget(for: sel)
}
}
复制代码
这里我只作了简单的处理,感兴趣的小伙伴能够继续拓展,好比为了不崩溃,能够在消息转发阶段使用 class_addMethod
方法加入本身实现的容错方法。
以上就是 中介者模式 的一个简单示例。而RxSwift中大量使用了 中介者,好比 Sink,来处理一些不方便暴露的方法,以及解耦各个对象之间的联系。如有不足之处,请评论指正。