美文网首页Swift编程
使用纯Swift编写一个事件代理

使用纯Swift编写一个事件代理

作者: fuadam1982 | 来源:发表于2019-07-02 00:07 被阅读29次

在日常开发中我们经常会遇到这样的场景,有很多模块的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功能需要解决三个问题:

  1. pure swift中的protocol没有@optional,也即没有respondsToSelector的能力
  2. proxy是一个通用的实现,而delegate中的方法签名是各不相同的,也即没有@selector的能力
  3. swift是泛型+类型推导的编程范式,而各回调方法的参数类型是完全不同的。另外proxy内部维护target列表的类型必然是Any——也就是无类型。这就导致无法通过「as」强转出原方法签名并执行,也即没有invocation等能力

因为上述的三个理由,在pure swift的实现版本中代码冗余一些是必然的但至少能实现。下面我们来逐个解决上面列出的问题:

  1. 对于第一个问题,所有的静态语言都只能用继承(多态)来解决。还好swift的extension(或者其它支持类mixin的静态语言)可以借助编译器做到多态而又不会侵入到target中(所有单继承的OO语言,对于继承是即爱又恨)
  2. 第二个问题的标准做法还是继承,通过一个proxy的子类实现所有会用到的delegate的协议。在swift中则是通过extension来包装proxy的「通用」invoke方法。这个方式可以很方便的解决方法签名的强类型校验,但缺点是delegate中的每一个方法都需要包装一遍,其实这只能算是强类型语言的特点不能算缺陷。另外还可以用swift 5.0中的@dynamicCallable + @dynamicmemberlookup实现对delegate中方法的访问「动态」访问,只不过实现起来还会更繁琐一些
  3. 第三个问题与第二个问题是紧密相连的,如果采用@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)
        }
    }
}

简单说明一下:

  1. invoke为proxy的通用执行方法,通过#function获取方法签名
  2. invoking用于处理事件流,target在具体回调时可以通过suspend来控制是否允许后续的事件流挂起或者终止
  3. 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()

相关文章

网友评论

    本文标题:使用纯Swift编写一个事件代理

    本文链接:https://www.haomeiwen.com/subject/zlxwcctx.html