关于面向协议编程之协议扩展与动态、静态派发

在Swift 2发布时,苹果将Swift定义为一门面向协议编程的语言,协议(Protocol)在Swift中被赋予了更加强大、灵活的功能。相比于Objective-C的协议,Swift的协议不仅可以被用做代理,也可以用作对接口的抽象,代码的复用。这里主要介绍下协议的扩展与协议的动态、静态派发。

面向协议编程

在面向对象编程中,所有的功能都被封装在一个个对象中,而在面向协议编程中,功能是被定义在一个个协议中。比如吃东西,我首先要定义一个对象、比如人,然后提供一个吃东西的方法,如果我再创建一条狗,也有吃东西的动作,这个时候要么写分别写两套吃东西的代码,要么对人、狗进一步抽象,抽出更基础的动物基类,把吃东西的方法写在动物基类里,让人、狗继承自动物。

而面向协议编程则不同,我们只需要定义一个吃东西的协议,所有需要吃东西动作的类都遵循这个协议即可,不需要为了这一个动作而继承一个基类。这使得接口的定义更加灵活,轻便。同时通过协议扩展,我们可以为定义的接口提供默认现实,达到代码复用的目的。

打一个比喻,面向对象编程的继承像套娃娃,一层继承就是套一个娃娃,面向协议编程就像搭积木,一个个协议就是一个个积木,一个协议就是一块积木,通过把多个积木组合起来实现不同的功能。

协议的扩展和协议的动态、静态派发

在Swift 2中,协议加入了强大的扩展功能,通过扩展,一是可以给协议定义中的方法提供默认的实现,二是可以在扩展中添加新的方法。这使得Swift中的协议能通过协议扩展实现代码的复用。

通过协议扩展进行代码共享与继承相比,有以下几个优势:

  • 不需要强制使用某个父类
  • 可以让已经存在的类型遵循协议
  • 协议既可以用于类,也可以用于结构体和枚举
  • 通过协议,不需要处理super方法的调用问题

使用协议扩展时需要注意的一点是,在协议定义声明了的方法是动态派发的,而只是在协议扩展中添加,并没有在定义中声明的方法,是静态派发的。

动态派发的作用就是,在执行时,会先去寻找具体类型中对这个方法的现实,如果有会调用这个类型的实现,如果没有,再调用协议扩展中的实现。而静态派发,就是调用者声明的类型是什么,就去类型中找这个方法的实现,如果类型声明为协议,即使这个类型中有这个方法的现实,也只会调用协议扩展中的实现。

比如我们定义一个Animal的协议,定义了一个eat的方法,在扩展中实现了这个方法,并扩展了一个run方法,但没有在定义中声明run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

protocol Animal {
func eat()
}

extension Animal {
func eat() {
print("Animal eat")
}

func run() {
print("Animal run")
}
}

接下来我们定义一个Person

1
2
3
4
5
6
7
8
9
10
struct Person: Animal {

func eat() {
print("Person eat")
}

func run() {
print("Person run")
}
}

然后用Person创建一个Animal类型的实例,分别调用eat, run方法:

1
2
3
4

let animal: Animal = Person()
animal.eat()
animal.run()

会发现,执行eat方法时,调用的是Person的实现,执行run方法时,调用的是Animal扩展中的实现。如果把animal的类型指定去掉,通过类型推断,它就是Person类的实例,这时执行run方法,就会调用Person的实现。这就是协议的动态派发和静态派发。

需要注意的一点是,如果你在扩展中为协议的某些方法提供了默认实现,最好要在文档中说明遵循这个协议,需要实现哪些方法,不然一旦协议的方法变多,变复杂,没有文档说明,很难搞清楚到底要实现哪些方法。

协议的关联类型和Self

带有关联类型的协议和普通的协议是不同的,普通的协议可以作为一个单独的类型,像上面的例子中Animal就是普通协议,let animal: Animalanimal就能指定它是Animal类型,但如果带有关联类型,就不能被当做独立的类型来使用。和关联类型一样,带有Self的协议也是不能被当做独立的类型来使用。

坚持原创技术分享,您的支持将鼓励我继续创作!