标准库中的协议
Swift标准库广泛使用的协议可能会让你感到惊讶。理解协议在Swift中扮演的角色可以帮助您编写干净的、解耦的“Swifty”代码。
Equatable
一些简单的代码,比如两个整数使用“==”操作符进行比较:
let a = 5
let b = 5
a == b // true
String类型也是可以的
let swiftA = "Swift"
let swiftB = "Swift"
swiftA == swiftB // true
但是你不能在any类型上使用“==”。假设你想要定义一个结构体代表一个队的得分情况。并对比两个分数是否相等。
struct Record {
var wins: Int
var losses: Int
}
let recordA = Record(wins: 10, losses: 5)
let recordB = Record(wins: 10, losses: 5)
recordA == recordB // Build error!
你不能把“==”应用到刚刚定义的结构体中,但是你可以使用扩展将“==”用到自己的代码里,就像swift标准库中使用“==”比较Int和String一样。
Int和String都遵循标准库中的Equatable协议,其中的一个单一静态方法:
protocol Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool
}
你可以将此协议应用于Record:
extension Record: Equatable {
static func ==(lhs: Record, rhs: Record) -> Bool {
return lhs.wins == rhs.wins &&
lhs.losses == rhs.losses
}
}
在这里,你定义(或重载)==运算符来比较两个Record实例。如果他们有相同的赢和输的数目,两个记录就是相等的。
现在,你可以像比较Int和String一样,使用==比较两个Record实例。
recordA == recordB // true
Comparable
一个Equatable的子协议是可比较的:
protocol Comparable: Equatable {
static func <(lhs: Self, rhs: Self) -> Bool
static func <=(lhs: Self, rhs: Self) -> Bool
static func >=(lhs: Self, rhs: Self) -> Bool
static func >(lhs: Self, rhs: Self) -> Bool
}
除了等式运算符==外,Comparable要求你重载类型的比较运算符<、<=、>和>=。实际上,你通常只提供<,因为标准库可以使用==和<实现为你实现<=、>和>=。
使Record采用如下所示的Comparable方式:
extension Record: Comparable {
static func <(lhs: Record, rhs: Record) -> Bool {
if lhs.wins == rhs.wins {
return lhs.losses > rhs.losses
}
return lhs.wins < rhs.wins
}
}
如果第一个Record的赢球数少于第二个Record,或者赢球数相等,但输球数较大,则<将实现一个Record小于另一个Record。
“自由”方法
虽然==和<本身就很有用,但Swift库为遵循Equatable和Comparable的类型提供了许多“ 自由”函数和方法。
对于你定义的任何包含Comparable类型的集合,例如数组,你可以访问标准库中一部分的方法,例如sort():
let teamA = Record(wins: 14, losses: 11)
let teamB = Record(wins: 23, losses: 8)
let teamC = Record(wins: 23, losses: 9)
var leagueRecords = [teamA, teamB, teamC]
leagueRecords.sort()
// {wins 14, losses 11}
// {wins 23, losses 9}
// {wins 23, losses 8}
由于你已经给了Record对两个值进行比较的能力,因此标准库具有对Record数组进行排序所需的所有信息!
正如你所看到的,实现Equatable和Comparable可以为你提供相当多的工具:
leagueRecords.max() // {wins 23, losses 8}
leagueRecords.min() // {wins 14, losses 11}
leagueRecords.starts(with: [teamA, teamC]) // true
leagueRecords.contains(teamA) // true
其他有用的协议
虽然学习整个Swift标准库对你作为Swift开发人员的成功并不重要,但是你会发现几乎在任何项目中都有其他一些重要的协议。
Hashable
Hashable协议是一个Equatable的子协议,字典的键的类型必须遵循它。
protocol Hashable : Equatable {
var hashValue: Int { get }
}
Hash值可以帮助你快速找到集合中的元素。为了使其工作,他们认为==的值也必须具有相同的Hash值。因为Hash值的数量是有限的,所以不相等的值有相同哈希值的概率是有限的。
哈希值背后的数学是相当复杂的,但重要的是要记住,相等的值必须有相等的哈希值。大多数Swift类型都采用Hashable,所以你通常可以依赖属性的哈希值来构建你的哈希值。
例如:
class Student {
let email: String
var firstName: String
var lastName: String
init(email: String, firstName: String, lastName: String) {
self.email = email
self.firstName = firstName
self.lastName = lastName
} }
extension Student: Equatable {
static func ==(lhs: Student, rhs: Student) -> Bool {
return lhs.email == rhs.email
}
}
extension Student: Hashable {
var hashValue: Int {
return email.hashValue
}
}
当学生注册时,你给他们分配一个电子邮件地址,并使用这个地址来唯一地识别学生。如果他们有相同的电子邮件地址,学生对象就被认为是相等的(意思是他们描述相同的学生)。因此,学生的哈希值必须基于学生的电子邮件地址。由于字符串符合Hashable,你可以简单地返回电子邮件的哈希值,而不必自己计算哈希值。
你现在可以使用学生Student类型作为字典Dictionary的键key:
let john = Student(email: "[email protected]", firstName:
"Johnny", lastName: "Appleseed")
let lockerMap = [john: "14B"]
CustomStringConvertible
非常方便的CustomStringConvertible协议可以帮助你记录和调试实例。
当你在一个实例(如学生)上调用print()方法时,Swift会打印一个含糊的描述:
print(john)
// Student
CustomStringConvertible协议只有一个描述属性的要求。此属性自定义实例在print()语句和调试器中的显示方式:
protocol CustomStringConvertible {
var description: String { get }
}
通过在Student上使用CustomStringConvertible,你可以提供更可读的表现形式:
extension Student: CustomStringConvertible {
var description: String {
return "\(firstName) \(lastName)"
}
}
print(John)
CustomDebugStringConvertible与CustomStringConvertible类似:它的行为与CustomStringConvertible完全一样,不过它还定义了一个debugDescription。使用CustomDebugStringConvertible的debugPrint()只在调试配置中打印输出。
要点
•协议定义了一个类、结构和枚举可以采用的契约。
•使用协议,类型需要通过实现协议的所有方法和属性来遵循协议。
•一个类型可以采用任意数量的协议
•你可以使用扩展来实现协议。
•Swift标准库广泛使用协议。你可以在自己的命名类型上使用它们,比如Equatable和Hashable。