协议(三)

标准库中的协议

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。

你可能感兴趣的:(协议(三))