协议(二)
协议的实现
正如你已经看到的,当你声明的类型符合协议时,你必须实现协议中声明的所有需求:
class Bike: Vehicle {
var peddling = false
var brakesApplied = false
func accelerate() {
peddling = true
brakesApplied = false
}
func stop() {
peddling = false
brakesApplied = true
}
}
Bike实现了在Vehicle中定义的所有方法。如果没有定义加速accelerate()或stop(),则会提示一个构建错误。
定义协议保证任何遵守协议的类型都将具有你在协议中定义的所有成员。
属性的实现
回想一下,协议中的属性包含get也可能包含set需求,符合条件的类型必须至少符合这些需求。
将自行车 Bike升级为一辆车 WheeledVehicle:
class Bike: WheeledVehicle {
let numberOfWheels = 2
var wheelSize = 16.0
var peddling = false
var brakesApplied = false
func accelerate() {
peddling = true
brakesApplied = false
}
func stop() {
peddling = false
brakesApplied = true
}
}
车轮numberOfWheels常数满足了要求。轮径wheelSize变量满足get和set的要求。
协议并不关心如何实现它们的需求,只要你实现它们。你可以选择的实现get方法:
•一个恒定的存储属性
•一个变量存储属性
•只读计算属性
•读写计算属性
实现get和set属性的选择仅限于变量存储属性或读写计算属性。
协议中相关类型
你还可以将关联类型添加为协议成员。当在协议中使用associatedtype时,你只是在声明该协议中使用了一个类型,而没有指定它应该是什么类型。应该由协议使用者来决定具体的类型。
你可以给类型赋予任意的名称,而无需指定它最终将是哪种类型:
protocol WeightCalculatable {
associatedtype WeightType
var weight: WeightType { get }
}
这将类型的决定委托给实现。
你可以在下面的两个例子中看到这是如何工作的:
class HeavyThing: WeightCalculatable {
// This heavy thing only needs integer accuracy
typealias WeightType = Int
var weight: Int {
return 100
}
}
class LightThing: WeightCalculatable {
// This light thing needs decimal places
typealias WeightType = Double
var weight: Double {
return 0.0025
}
}
在这些示例中,你使用typealias来显式地表示关联类型。这通常不是必需的,因为编译器可以推断出类型。在前面的示例中,WeightType的类型明确了关联类型应该是什么,因此可以删除typealias。
你可能已经注意到,WeightCalculatable协议根据关联类型而更改。注意,这可以防止你将协议作为简单的变量类型使用。
// Build error!
// protocol 'WeightCalculatable' can only be used as a generic
// constraint because it has Self or associated type requirements.
let weightedThing: WeightCalculatable = LightThing()
在下一章中,你将了解到所有关于通用约束的内容。
实现多种协议
一个类只能从一个类继承—这是“单个继承”的属性。相反,类可以采用任意多的协议!
假设你没有创建继承自Vehicle的WheeledVehicle协议,而是创建了自己的wheels协议。
protocol Wheeled {
var numberOfWheels: Int { get }
var wheelSize: Double { get set }
}
class Bike: Vehicle, Wheeled {
// Implement both Vehicle and Wheeled
}
协议支持“多个遵循”,因此你可以将任意数量的协议应用于定义的类型。在上面的示例中,Bike类现在必须实现Vehicle车辆和Wheeled轮式协议中定义的所有成员。
组合协议
在前一节中,你学习了如何实现多个协议。有时,需要一个函数来获取必须符合多个协议的数据类型。这就是协议组合的作用。假设你需要一个需要访问Vehicle车辆协议的stop()函数和Wheeled轮式协议的numberOfWheels属性的函数。你可以使用&组合操作符来实现这一点。
func roundAndRound(transportation: Vehicle & Wheeled) {
transportation.stop()
print("The brakes are being applied to \
(transportation.numberOfWheels) wheels.")
}
roundAndRound(transportation: Bike())
// The brakes are being applied to 2 wheels.
扩展和遵守协议
你还可以使用扩展去使用协议。这可以向你不一定拥有的类型添加协议。考虑下面这个简单的例子,它将自定义协议添加到字符串中:
protocol Reflective {
var typeName: String { get }
}
extension String: Reflective {
var typeName: String {
return "I'm a String"
}
}
let title = "Swift Apprentice!"
title.typeName // I'm a String
即使字符串是标准库的一部分,你仍然能够使字符串采用并实现反射Reflective协议。
使用扩展的另一个好处是,你可以很好地将使用协议与必需的方法和属性组合在一起,而不是让一堆协议在类型定义中弄得乱七八糟。
下面的代码将使用Vehicle车辆扩展到AnotherBike另一辆自行车上:
class AnotherBike: Wheeled {
var peddling = false
let numberOfWheels = 2
var wheelSize = 16.0
}
extension AnotherBike: Vehicle {
func accelerate() {
peddling = true
}
func stop() {
peddling = false
}
}
这个扩展拥有Vehicle的accelerate和stop方法。如果你要从AnotherBike另一个自行车中删除车辆协议,你可以简单地删除此协议的扩展。
需要引用语义
协议可以通过值类型(结构和枚举)和引用类型(类)来使用,因此你可能会怀疑协议是否具有引用或值语义
真相是……视情况而定!如果你有一个为协议类型的变量分配的类或结构的实例,它将表示与它定义的类型相匹配的值或引用语义。
为了说明这一点,下面举一个命名协议的简单示例,它实现为一个结构体和一个类:
protocol Named {
var name: String { get set }
}
class ClassyName: Named {
var name: String
init(name: String) {
self.name = name
}
}
struct StructyName: Named {
var name: String
}
如果你给一个命名变量分配一个引用类型的实例,你会看到引用语义的行为:
111
r named: Named = ClassyName(name: "Classy")
var copy = named
named.name = "Still Classy"
named.name // Still Classy
copy.name // Still Classy
同样,如果您分配一个值类型的实例,您将看到值语义的行为:
amed = StructyName(name: "Structy")
copy = named
named.name = "Still Structy?"
named.name // Still Structy?
copy.name // Structy
情况并不总是如此。你会注意到,在大多数情况下,Swift更喜欢值语义而不是引用语义。如果您正在设计一个由类完全采用的协议,那么最好在使用此协议作为类型时请求Swift使用引用语义
protocol Named: class {
var name: String { get set }
}
通过使用上面的类约束,你指出只有类可以采用此协议。这说明Swift应该使用引用语义。