众所周知,在Swift中我们可以使用 Any 来表示任意类型。充满好奇心的读者已经发现,Any
这个类型的定义十分奇怪,它是一个 protocol<>
的同名类型。
像protocol<>
这种形式的写法在Swift的日常使用中并不多见,这其实是Swift的接口组合
的用法。标准的语法形式是下面这样的:
ProtocolA & ProtocolB & ProtocolC
尖括号
内是具体
接口的名称
,这里表示将名称为ProtocolA, ProtocolB及ProtocolC的接口组合在一起的一个新的匿名接口
。实现这个匿名接口就意味着要同时实现
这三个接口
所定义的内容。所以说,这里的protocol
组合的写法和下面新声明的ProtocolD
是相同的:
protocol ProtocolD: ProtocolA, ProtocolB, ProtocolC {
}
那么在Any
定义的时候,里面什么都不写的protocol<>
是什么意思呢?从语义上来说,这代表一个“需要实现空接口的接口”
,其实就是任意类型
的意思了。
除了可以方便的表达空接口这一概念以外,protocol的组合
与新创建一个接口相比最大区别就在于其匿名
性。有时候我们可以借助这个特性写出清晰
的代码
。因为Swift
的类型组织
比较松散
的,你的类型可以由不同的extension
来定义实现
不同的接口
,Swif
t 也并没有要求
它们在同一个文件
中。这样,如果一个类型实现了很多接口,在使用这个类型的时候,我们很可能在不查询相关代码的情况下难以知道这个类型所实现的接口。
举个理想化的例子,比如我们有下面的三个接口,分别代表了三种动物叫的方式,而有一种“迷之动物”,同时实现了这三个接口:
protocol KittenLike {
func meow() -> String
}
protocol DogLike {
func bark() -> String
}
protocol TigerLike {
func aou() -> String
}
class MysteryAnimal: KittenLike, DogLike, TigerLike {
func meow() -> String {
return "meow"
}
func bark() -> String {
return "bark"
}
func aou() -> String {
return "aou"
}
}
现在我们想要检查某种动物作为宠物的时候的叫声的话,我们可能要重新定义一个叫作PetLike 的接口
,表明其实现 KittenLike 和 DogLike
;如果稍后我们又想检查某种动物作为猫科动物的叫声的话,我们也许又要去定义一个叫作 CatLike
的实现KittenLike和TigerLike
的接口,最后我们大概会写出这样的东西:
protocol PetLike: KittenLike, DogLike {
}
protocol CatLike: KittenLike, TigerLike {
}
struct SoundChecker {
static func checkPetTalking(pet: PetLike) {
// ...
}
static func checkCatTalking(cat: CatLike) {
// ...
}
}
虽然没有定义任何新的内容,但是为了实现这个需求,我们还是添加
了两个空protocol
,这可能会让人困惑,代码的使用者(也包括一段时间后的你自己)可能会去猜测PetLike和CatLike
的作用----其实它们除了标注以外并没有其他作用。借助protocol组合
的特性,我们可以很好的解决这个问题。protocol组合
是可以使用typealias
来命名
的,于是可以将上面的新定义protocol的部分换为:
typealias PetLike = KittenLike & DogLike
typealias CatLike = KittenLike & TigerLike
这样既保持了可读性,也没有多定义不必要的新类型。
另外,如果这两个临时接口我们只用一次的话,只要结合上下文理解起来不会有困难,我们完全可以直接将它们匿名话,变成下面这样:
struct SoundChecker {
static func checkPetTalking(pet: KittenLike & DogLike) {
// ...
}
static func checkCatTalking(cat: KittenLike & TigerLike) {
// ...
}
}
这样的好处是定义和使用的地方更加接近,这样在代码复杂的时候,读代码时可以少一些跳转
,多一些专注
。但是因为使用了匿名的接口组合,所以能表达的信息
毕竟少一些
。如果要实际使用这种方法的话,还是需要多多斟酌
。
虽然这一节已经够长了,不过我还是想多提一句关于实现多个接口
时接口内方法冲突
的解决方法。因为在Swift的世界中并没有规定不同接口
的方法不能重名
,所以重名现象是有可能出现的情况。比如有A和B两个接口,定义如下:
protocol A {
func bar() -> Int
}
protocol B {
func bar() -> String
}
这两个接口中bar()
只有返回值
的类型不同
。我们如果有一个类型Class
同时实现了A 和B,我们要怎么才能避免和解决调用冲突呢?
class Class: A, B {
func bar() -> Int {
return 1
}
func bar() -> String {
return "Hello"
}
}
这样一来,对于bar(),只要在调用前进行类型转换就可以了:
let instance = Class()
let num = (instance as A).bar()
let str = (instance as B).bar()
print(num) // 1
print(str) // Hello