Protocol 作为 Swift 生态的最重要的组成部分(没有之一),其搭建起了整个语言生态的各个组成部分。

相对于继承而言,Protocol 的几个比较直观的优势:

  1. 无需必须去强制继承某个类;
  2. 通过协议可以改造已经存在的类型;
  3. 适用范围更大一些,使得 struct 和 enum 这些值类型也能够继承能力
  4. Swift 语言本身是不支持多继承的,你需要花费精力去纠结继承哪个 class,一旦出现不同的能力分散在不同父类的情况,就更加纠结了,你甚至需要去修改原有的父类;
  5. 继承的话,还需要考虑 override 父类方法的问题,比如调用 super 方法的时机问题;

这篇文章想记录下 Protocol Extension 中的一点区别。

我们知道 Swift Protocol 中是没有 optional 关键字的,因此只要是在 Protocol 定义的方法,即为 Protocol Requirements,符合该协议的类型就必须实现,否则是不完整的。比如,我们自己实现了 TableView,定义了这么多 delegate 协议方法,要求调用方全都提供,就太不人道了😢

Protocol

因此,我们会通过对 Protocol 进行扩展,给该协议方法增加缺省实现,剩余的方法则是你需要调用方必须实现的方法。

Optional Protocol

如果针对某个协议 P,我们扩展 P 给其增加某个协议方法有两种形式:

  1. 直接在 Protocol 定义里定义新方法,该方法成为实现该协议必须实现的方法;
  2. 为 Protocol 增加扩展方法,该方法并不一定要求符合该协议的类型进行实现;

这两者有什么区别呢? 我们看代码

Static Dispatch

发现 s 调用的是我们为 S 结构体增加的扩展方法,而 s2 调用的是 P 协议的扩展方法,尽管实体对象的动态类型是 S,但是因为声明的静态类型不一样,调用结果也不同。

对于 1 中的方法,运行时进行动态派发,也就是真实根据其具体类型来做决议,2 中的方法是进行静态派发,也就是说你声明的该对象是什么类型,就调用该类型的方法。 以上述的代码为例,s1 的静态类型由编译器推导为 S,而 s2 的静态类型声明为 P ,因此二者在调用扩展协议方法时候的表现不一致。

如果想确保调用方法时候不因为静态类型不同而导致不同的结果,可以将协议扩展方法在 P 协议定义的地方加入,自此所有符合该协议的类型就必须强制实现该协议,此时该方法则是通过动态决议来派发了。

Dynamic Dispatch

而我们了解完这些之后,对上面 TableView 的栗子就有了更深刻的理解,Protocol 中的方法定义使得所有符合该协议的类型在运行时进行决议(也就是对象的动态类型),而这个过程中如果发现该对象已经实现了。