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