原文链接:https://www.raywenderlich.com/109156/introducing-protocol-oriented-programming-in-swift-2

在WWDC2015,苹果宣布了Swift的2.0版本,这个版本中加入了一些新的语言特性,帮助大家写出更好的代码。

在这些新特性中,最另人激动的是protocol extension,协议的扩展。在Swift最开始的版本中,你可以扩展class,struct,enum这些类型。而到了2.0版本,你还能够扩展protocol。

协议扩展乍一看可能只是一个很小的新特性,但是事实上,这个特性是很强大的,甚至能够改变你的编码风格。在这个教程中,你可以在明白协议扩展是如何运用的同时,了解面向协议编程的模式。

你也会同时看到开发Swift的队伍是如何运用协议扩展这个新特性去改进一些Swift的基本库,以及这些改动如何影响到你写的代码。

Getting Started

先创建一个PlayGround,在Xcode中选择File\New\PlayGround… 然后命名为SwiftProtocols。你可以选择任意的平台,因为这次的教程与平台无关,点击Next选择你想保存该文件的目录,最后点击Create完成创建。

打开你的playground,添加下面的代码

1
2
3
4
5
6
7
protocol Bird {
var name: String { get }
var canFly: String { get }
}
protocol Flyable {
var airspeedVelocity: Double { get }
}

这里定义了一个简单的协议Bird,他有两个成员变量namecanFly,同时也定义了协议Flayable,他只有一个成员变量airspeedVelocity

在一个一切协议优先的世界,你可能需要将Flyable作为你的基类,然后通过继承去定义类似鸟一样可以飞行的东西,比如飞机。注意:这里所有的一切都以协议作为起始点

当你开始定义具体的类型时,你将会见识到这句话是如何让整个系统变得更加的灵活。

Defining Protocol-conforming Types

将下面定义结构体的代码添加到你的playground里去。

1
2
3
4
5
6
7
8
9
10
struct FlappyBird: Bird, Flyable {
let name: String
let flappyAmplitude: Double
let flappyFrequency: Double
let canFly = true
var airspeedVelocity: Double {
return 3 * flappyFrequency * flappyAmplitude
}
}

这里定义了一个新的结构体叫做FlappyBird,他遵循了Bird和Flyable两个协议,成员变量airspeedVelocity是由flappyFrequency和flappyAmplitude两个成员相乘得到的。作为一个flappy,canFly理所当然的返回true值。

下一步,添加下面的结构体定义到playground的底部:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Penguin: Bird {
let name: String
let canFly = false
}
struct SwiftBird: Bird, Flyable {
var name: String { return "Swift \(version)" }
let version: Double
let canFly = true
// Swift is FAST!
var airspeedVelocity: Double { return 2000.0 }
}

企鹅虽然是鸟类,但是他不会飞。哈哈,所以你没有选择继承机制,不然就意味着所有的鸟类都会飞了。至于雨燕,显而易见是一种具有很快飞行速度的鸟类。

到这里你可能看到一些冗余,每种遵循Bird协议的类型都需要声明他canFlay的值。尽管这里已经有一个Flyable的协议了。

Extending Protocols With Default Implementations

通过协议扩展,你能够定义协议的默认行为,将下面的代码添加到Bird协议定义的下面

1
2
3
4
extension Bird where Self: Flyable {
// Flyable birds can fly!
var canFly: Bool { return true }
}

这个在Bird协议扩展定义了当类型同时遵循Flyable协议时,返回canFly的值为true。换句话说,每个遵循了Bird协议并同时遵循了Flyable协议的类型,不再需要将canFly显示的声明为ture了。

swift1.2 介绍了where将if let绑定的语法,然后swift2将这个语法应用到了根据条件去扩展一个协议这个功能中。

Why Not Base Classes?

swift的协议扩展和协议的默认实现看起来可能有点像其他语言的基类继承或者抽象类,但他提供了几个关键的优势。

  • 因为一个类型可以遵循多个协议,他们能够悲多个具有基础实现的协议所装饰。不像其他语言的多继承,协议继承不回引进多余的状态。
  • 协议能够被struct,enum,class所继承,而基类继承只能够被class类型所运用。

换句话说,协议扩展不仅能够为class类型提供了默认的行为,也能为struct和enum这些类型提供默认的行为。

你已经见识过了协议扩展在struct类型中的运用,下面我们看下对enum类型,协议扩展怎样的。将下面的代码添加到playground中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
enum UnladenSwallow: Bird, Flyable {
case African
case European
case Unknown
var name: String {
switch self {
case .African:
return "African"
case .European:
return "European"
case .Unknown:
return "What do you mean? African or European?"
}
}
var airspeedVelocity: Double {
switch self {
case .African:
return 10.0
case .European:
return 9.9
case .Unknown:
fatalError("You are thrown from the bridge of death!")
}
}
}

和其他的类型一样,你只需要定义正确的变量,所以UnladenSwallow遵循了这两个协议,正因为他同时遵循了这两个协议,所以他的canFly默认值是true!

Extending Protocols

也许协议扩展最常用的是扩展外部协议,或者那些在swift中已经定义好的库以及某些第三方库。

添加下面的代码到playground的底部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension CollectionType {
func skip(skip: Int) -> [Generator.Element] {
guard skip != 0 else { return [] }
var index = self.startIndex
var result: [Generator.Element] = []
var i = 0
repeat {
if i % skip == 0 {
result.append(self[index])
}
index = index.successor()
i++
} while (index != self.endIndex)
return result
}
}

这里定义了一个CollectionType的协议的扩展,提供了一个skip的新方法。方法在原数组中每隔skip个跳过一个元素,并返回所有跳过的元素组成的数组。

CollectionType在swift中是一个被array和dictionary遵循的协议,这意味着这个新的方法存在与任何一个遵循CollectionType的类型中。将下面的代码加到playground底部。

1
2
3
4
5
6
7
8
9
let bunchaBirds: [Bird] =
[UnladenSwallow.African,
UnladenSwallow.European,
UnladenSwallow.Unknown,
Penguin(name: "King Penguin"),
SwiftBird(version: 2.0),
FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)]
bunchaBirds.skip(3)

在这里你定义了一个鸟类的数组,包括了所有你定义的类型,并且你也可以调用skip方法。

Extending Your Own Protocols

除了添加新的方法给swift的默认类,你也可以通过协议扩展,定义类型的默认行为。

将Bird协议改写为继承自BooleanType的协议

1
protocol Bird: BooleanType {

如果某个类型遵循BooleanType协议,则你必须要有个boolValue变量,使他表现的像一个Boolean类型。是不是这意味着你需要在每个当前或者未来的Bird协议中都要实现BooleanType协议中定义的变量和方法呢,不然,你可以用下面一种更加方便的协议扩展的方式实现。

1
2
3
4
5
extension BooleanType where Self: Bird {
var boolValue: Bool {
return self.canFly
}
}

这样就能保证每个继承Bird协议的类型都能表现得像一个Boolean变量。

运行下面的代码试试看

1
2
3
4
5
if UnladenSwallow.African {
print("I can fly!")
} else {
print("Guess I’ll just sit here :[")
}

你会看到 I can fly,尽管你只用了UnladenSwallow.African作为if的条件。

Effects on the Swift Standard Library

至于这部分,简单说就是在swift1.0中,原来的map函数只能应用在一些特定的预定于类型中。而在2.0中,由于实现了协议扩展,你可以将map扩展到CollectionType中,这样就可以对所有继承CollectionType协议的类型中使用map方法了。