在日常开发中我们经常会遇到这样的场景,有很多模块的delegate需要通过一个公共类来转发回调事件。比如采用MVP模式开发一个复杂的UI交互,其中(许多)View要通过Presenter来转发网络回调、文件访问、数据库等多种不同的Delegate的回调事件。标准做法是在Presenter中实现各个不同的Delegate,然后再转发给(多个)View。这样做繁琐且代码重复,更重要的是不方便对多个View做回调控制。例如:用户在首次观看视频时,点击「退出」按钮会响应「视频退出事件」此时会弹出一个confirm,如果选择「取消」则中断后续的事件回调。对于这样的场景使用proxy是最方便的。如果有这么一个Proxy,则Presenter不需要转发一次事件回调,而只要将(多个)view添加到proxy接收Delegate的回调就可以了。如果用ObjeC来实现proxy是非常简单的,只需要实现三个runtime方法即可:
/** 判断是否可以响应Selector */
- (BOOL)respondsToSelector:(SEL)aSelector {
for (id target in self.targets) {
if ([target respondsToSelector:aSelector]) {
return YES;
}
}
return NO;
}
/** 解析方法签名 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = nil;
for (id target in self.targets) {
if ((signature = [target methodSignatureForSelector:selector])) {
break;
}
}
return signature;
}
/** 消息转发 */
- (void)forwardInvocation:(NSInvocation *)invocation {
for (id target in targets) {
// 这里可添加更多的「控制」
[context invokeWithTarget:target];
}
}
但是对于pure swift来说,因为动态能力几乎为零——使用Mirror只能做些简单的属性操作而不能对方法做反射操作,所以要实现类似上面ObjC版的proxy功能需要解决三个问题:
- pure swift中的protocol没有@optional,也即没有respondsToSelector的能力
- proxy是一个通用的实现,而delegate中的方法签名是各不相同的,也即没有@selector的能力
- swift是泛型+类型推导的编程范式,而各回调方法的参数类型是完全不同的。另外proxy内部维护target列表的类型必然是Any——也就是无类型。这就导致无法通过「as」强转出原方法签名并执行,也即没有invocation等能力
因为上述的三个理由,在pure swift的实现版本中代码冗余一些是必然的但至少能实现。下面我们来逐个解决上面列出的问题:
- 对于第一个问题,所有的静态语言都只能用继承(多态)来解决。还好swift的extension(或者其它支持类mixin的静态语言)可以借助编译器做到多态而又不会侵入到target中(所有单继承的OO语言,对于继承是即爱又恨)
- 第二个问题的标准做法还是继承,通过一个proxy的子类实现所有会用到的delegate的协议。在swift中则是通过extension来包装proxy的「通用」invoke方法。这个方式可以很方便的解决方法签名的强类型校验,但缺点是delegate中的每一个方法都需要包装一遍,其实这只能算是强类型语言的特点不能算缺陷。另外还可以用swift 5.0中的@dynamicCallable + @dynamicmemberlookup实现对delegate中方法的访问「动态」访问,只不过实现起来还会更繁琐一些
- 第三个问题与第二个问题是紧密相连的,如果采用@dynamicCallable + @dynamicmemberlookup方式则可以使用([String : Any?]) -> (Instance) -> ()对delegate的各方法做currying。如果采用包装proxy的「通用」invoke方法的实现方式则可以通过(T) -> ()对个方法做currying,其中T为delegate协议类型。
结合下面的代码会更容易理解上面的解释:
/**
* 通过事件代理
TODO:
1. 并发控制
2. 设定target响应优先级
3. 对target设定所在的回调线程
*/
class Proxy {
/** 缓存转发的target列表 */
private var targets: [Any] = []
/** 记录事件流挂起/终止情况 */
private var suspendOfEvents: [String: (Bool, Bool)] = [:]
/** 记录恢复事件流时对应target的Index和需要执行的回调closure */
private var resumeOfEvents: [String: (Int, (Any) -> () -> ())] = [:]
/** 添加目标对象 */
func addTarget(target: Any) {
targets.append(target)
}
/** 挂起事件流 */
@discardableResult
func suspend(signature: String = #function,
isInterrupt: Bool = false) -> ((Bool) -> ())? {
if suspendOfEvents.keys.contains(signature) {
// 标记挂起
suspendOfEvents[signature] = (true, isInterrupt)
// 将resume包装成(isInterrupt) -> (),便于外部使用
return {
[weak self] isInterrupt in
if let self = self {
self.resume(signature: signature, isInterrupt: isInterrupt)
}
}
} else {
return nil
}
}
/** 恢复事件流 */
func resume(signature: String, isInterrupt: Bool = false) {
if let (idx, handler) = resumeOfEvents[signature] {
// 标记恢复
suspendOfEvents[signature] = (false, isInterrupt)
// 继续执行后续的事件流
invoking(signature: signature, index: idx, handler: handler)
}
}
/** 通用的事件回调执行器 */
func invoke<T>(signature: String = #function,
apply: @escaping (T) -> ()) {
// 删除未执行完的事件流
removeSuspendEvent(signature: signature)
// 记录执行情况
suspendOfEvents[signature] = (false, false)
// 执行事件流,将外部传入的apply(用于实际的方法调用)包装成(target) -> () -> ()
invoking(signature: signature) { (target: Any) in
return {
// 利用as保证类型安全,T为具体的协议类型
(target as? T).map(apply)
}
}
}
/** 处理事件流的执行、挂起、恢复 */
private func invoking(signature: String,
index: Int = 0,
handler: @escaping (Any) -> () -> ()) {
// 迭代执行事件流
for idx in index..<targets.count {
let target = targets[idx]
// 查询挂起、中断状态
if let (isSuspend, isInterrupt) = suspendOfEvents[signature] {
if isInterrupt {
// 事件流中断
print("cancel \(signature) at \(type(of: target))")
break
} else if isSuspend {
// 挂起事件流程
resumeOfEvents[signature] = (idx, handler)
print("suspend \(signature) at \(type(of: target))")
continue
}
}
// 执行事件流
handler(target)()
}
}
/** 删除执行中的事件流 */
private func removeSuspendEvent(signature: String) {
if suspendOfEvents.keys.contains(signature) {
resumeOfEvents.removeValue(forKey: signature)
suspendOfEvents.removeValue(forKey: signature)
}
}
}
简单说明一下:
- invoke为proxy的通用执行方法,通过#function获取方法签名
- invoking用于处理事件流,target在具体回调时可以通过suspend来控制是否允许后续的事件流挂起或者终止
- resume方法因为需要传入signature比较麻烦,所以在suspend中通过currying包装resume简化外部使用
下面为测试代码 —— 协议与Targets部分:
// 代理实例
var proxy = Proxy()
// 用于测试的协议
protocol Testable {
func say()
func hello(name: String)
}
// 为Testable协议提供默认的实现
extension Testable {
func say() {
print("Testable say()")
}
func hello(name: String) {
print("Testable hello(name:)")
}
}
struct Boo : Testable {
func say() {
print("boo say()")
// 挂起事件流
proxy.suspend()
}
}
struct Foo : Testable {
func say() {
print("Foo say()")
}
func hello(name: String) {
print("foo hello \(name)")
}
}
// 向代理添加target
proxy.addTarget(target: Boo())
proxy.addTarget(target: Foo())
下面为测试代码 —— 事件源部分:
/** 用于模拟的事件源 */
struct EventsSource {
let proxy: Proxy
}
/** 分组协议 */
protocol Groupable { }
/** 实现「分组」协议 */
extension EventsSource : Groupable { }
/** 「事件回调」分组 */
struct Eventer<Subject: Groupable> {
var subject: Subject
init(subject: Subject) {
self.subject = subject
}
}
extension Groupable {
var events: Eventer<Self> {
get {
return Eventer(subject: self)
}
}
}
/** 包装proxy的通用invoke */
extension Eventer where Subject == EventsSource {
func say() {
// target: Testable用于让编译器推断泛型类型
self.subject.proxy.invoke { (target: Testable) in
target.say()
}
}
func hello(name: String) {
// 因为无法直接获得原方法的签名,所以要用(T) -> ()进行currying
self.subject.proxy.invoke { (target: Testable) in
target.hello(name: name)
}
}
}
上面的测试代码在定义EventsSource后,使用曾经分享过的「分组」技巧来包装Proxy中的invoke方法。
let source = EventsSource(proxy: proxy)
print(">>> call say ...")
source.events.say()
print(">>> call hello ...")
source.events.hello(name: "fh")
print(">>> resume say ...")
proxy.resume(signature: "say()")
// 执行结果如下:
>>> call say ...
boo say()
suspend say() at Foo
>>> call hello ...
Testable hello(name:)
foo hello fh
>>> resume say ...
Foo say()
网友评论