使用协议扩展在 Swift 中进行事件调度

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

自 WWDC 以来,诸如 SketchyTech David Owens Ray Wenderlich 之类的博主已经写了很多关于 Swift 中面向协议编程的文章,我认为是时候发表自己的看法了。

在 ActionScript 中使用事件调度 多年后,协议扩展似乎是在 Swift 中实现类似模式的完美技术。实际上,协议扩展提供了直接的优势,即我可以将事件分派添加到任何类型的对象,而无需该对象扩展基类。例如,不仅用户界面组件可以调度事件,值对象和数据结构也可以: 非常适合 MVVM 模式 ,其中视图可以对视图模型上的事件做出反应以更新自身。

我的项目 Protocol Extension Event Dispatcher 包含一个演示应用程序,其中包含一些用户界面组件:滑块、步进器、标签和按钮。有一个“模型”:一个整数,当它的值通过这些组件发生变化时,它会调度一个变化事件。最终结果是,当用户与任何组件交互时,整个用户界面都会通过事件进行更新,以反映变化。

这并不是要完整地实现 Swift 中的事件调度,而是演示 Swift 中面向协议编程的可能性。如需更完整的版本,请查看 ActionSwift

让我们来看看我的代码是如何工作的。首先,我有我的协议 EventDispatcher,它定义了一些方法。这是一个类协议,因为我们希望调度程序是一个单一的引用对象:



 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}



符合 EventDispatcher 的对象的每个实例都需要一个小的事件侦听器存储库,我将其存储为一个字典,其中事件类型作为键,一组事件处理程序作为值。

第一个绊脚石是扩展可能不包含存储的属性。有几个选项可以解决这个问题:我可以创建一个全局存储库,或者我可以使用 objc_getAssociatedObject 和 objc_setAssociatedObject。这些功能都是我使用一些简单的语法将事件侦听器附加到每个 EventDispatcher 实例。我的 addEventListener 默认实现的代码如下所示:


 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


对于给定的类型和事件处理程序,我检查是否存在现有的 EventListeners 对象,如果存在,我检查该对象是否具有该类型的条目并相应地创建或更新值。一旦我有了最新的 EventListeners 对象,我就可以用 objc_setAssociatedObject 将它写回。

以类似的方式,对于 dispatchEvent(),我查询关联对象,检查事件类型的处理程序,如果有则执行它们:


 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


我创建了一个简单的包装器,它利用泛型允许任何数据类型在发生变化时分派事件:


 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


我的演示应用程序使用 DispatchingValue 来包装一个整数:



 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


...通过添加事件侦听器在更改时更新用户界面控件:


 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


我还在 UIControl 上创建了一个扩展,使所有 UI 控件都符合 EventDispatcher 并调度更改和点击事件:


 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


因此,例如,我的滑块可以在用户更改其值时更新 dispatchingValue:


 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


...这又将调用 dispatchingValueChangeHandler 并更新其他用户界面组件。我的重置按钮在点击时将 dispatchingValue 的值设置为零:


 protocol EventDispatcher: class
{

    func addEventListener(type: EventType, handler: EventHandler)



    func removeEventListener(type: EventType, handler: EventHandler)



    func dispatchEvent(event: Event)

}


我希望这篇文章能让您领略到面向协议编程所提供的不可思议的力量。再一次,我的项目可以在 我的 GitHub 存储库 中找到。享受!

相关文章