在 Swift 中,Comparable 协议是一种用于实现对象排序的协议。通过让类型遵守 Comparable,可以实现常见的比较运算符(例如 <、<=、> 和 >=)的功能。
本篇作为基础的知识拓展文章,如果不理解使用Comparable协议的场景,可以看一下《Swift通过实现Comparable协议定义比较规则》这篇文章。
协议定义
Comparable 是一个继承自 Equatable 的协议,因此必须实现 == 运算符,还需要实现以下核心方法:
public protocol Comparable: Equatable {
static func < (lhs: Self, rhs: Self) -> Bool
}
Equatable 的要求:
需要实现 == 运算符,表示两个值是否相等。
Comparable 的要求:
需要实现 < 运算符,表示第一个值是否小于第二个值。
一旦实现了 <,Swift 会自动为该类型生成其他比较运算符(<=、> 和 >=)。
如何遵守 Comparable 协议
以下是一个简单示例,定义一个 User 类型并使其遵守 Comparable 协议:
struct User: Comparable {
var firstName: String
var lastName: String
// 实现 Equatable 的要求
static func == (lhs: User, rhs: User) -> Bool {
lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
}
// 实现 Comparable 的要求
static func < (lhs: User, rhs: User) -> Bool {
if lhs.lastName == rhs.lastName {
return lhs.firstName < rhs.firstName
} else {
return lhs.lastName < rhs.lastName
}
}
}
代码解释
1、== 运算符:
确保 User 类型的两个实例只有在 firstName 和 lastName 都相同时才被认为是相等的。
2、< 运算符:
如果 lastName 相同,则比较 firstName。
如果 lastName 不同,则以 lastName 为主进行排序。
使用 Comparable 排序
遵守 Comparable 协议的类型可以直接用于排序操作,例如:
let users = [
User(firstName: "Arnold", lastName: "Rimmer"),
User(firstName: "Kristine", lastName: "Kochanski"),
User(firstName: "David", lastName: "Lister"),
]
let sortedUsers = users.sorted() // 自动按 Comparable 协议定义的规则排序
for user in sortedUsers {
print("\(user.lastName), \(user.firstName)")
}
输出结果:
Kochanski, Kristine
Lister, David
Rimmer, Arnold
注意事项
1、确保比较逻辑一致性:
Comparable 中的 < 实现应该与 Equatable 中的 == 一致。如果 a == b 为 true,则 a < b 和 b < a 都应该为 false。
2、自定义排序逻辑:
如果不想实现 Comparable 协议,也可以通过 sorted(by:) 使用自定义的闭包排序。
let sortedUsers = users.sorted {
if $0.lastName == $1.lastName {
return $0.firstName < $1.firstName
} else {
return $0.lastName < $1.lastName
}
}
但是可能会存在代码重复等问题,具体细节可见《Swift通过实现Comparable协议定义比较规则》。
3、继承关系:
如果类型有继承(例如使用类),确保所有子类的排序逻辑也能正确继承或覆盖。
遵循Equatable协议
前面的代码中提及类型遵循 Comparable 协议时,Swift 隐式要求类型同时遵循 Equatable,而 Swift 会根据类型的存储属性自动合成 Equatable 的实现,只要类型的所有存储属性本身也是 Equatable 的。
// 实现 Equatable 的要求
static func == (lhs: User, rhs: User) -> Bool {
lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
}
为什么 Swift 可以自动生成 Equatable?
下面的代码中,Swift结构只遵循Comparable协议,而没有显式声明Equatable协议,但是并没有报错。
struct User: Identifiable, Comparable{
let id = UUID()
var firstName: String
var lastName: String
static func <(lhs: User, rhs: User) -> Bool {
lhs.lastName > rhs.lastName
}
}
UUID 类型和 String 类型都遵循了 Equatable 协议,因此 Swift 能够为 User 自动生成 == 的实现。
自动合成的 == 会逐一比较类型的所有存储属性:当所有存储属性相等时,两个实例被认为相等。
隐式生成的 Equatable 实现
假如 Swift 展开了自动生成的 Equatable 实现,等价于以下代码:
static func ==(lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id &&
lhs.firstName == rhs.firstName &&
lhs.lastName == rhs.lastName
}
这就是为什么没有显式实现 Equatable,代码仍然可以正常运行。
Comparable 的关系
Comparable 依赖 Equatable,这是因为比较逻辑需要基于某种相等性的判断。例如:
如果 a < b 为 true,a == b 必须为 false。
如果 a == b 为 true,则 a < b 和 b < a 必须都为 false。
因此,Swift 要求所有遵循 Comparable 的类型必须先满足 Equatable 的要求。
如果需要自定义 Equatable
尽管 Swift 自动合成了 Equatable,仍然可以手动实现它,比如让 User 的比较逻辑只依赖于 firstName 和 lastName,而忽略 id:
static func ==(lhs: User, rhs: User) -> Bool {
return lhs.firstName == rhs.firstName &&
lhs.lastName == rhs.lastName
}
这样,两个 User 实例的 id 即使不同,只要 firstName 和 lastName 相同,它们仍然会被认为是相等的。
总结
通过实现 Comparable 协议,类型可以更方便地参与排序、搜索等操作,并保持代码的简洁性和一致性。
代码中没有显式实现 Equatable,是因为 Swift 为 User 类型自动生成了 Equatable。
自动生成的 Equatable 会比较类型的所有存储属性。
如果需要自定义 Equatable 的逻辑,完全可以手动实现。
知识扩展
Comparable协议的定义
public 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
}
这段代码是Xcodez中关于Comparable的代码定义,隐藏了注释代码。
协议声明
public protocol Comparable : Equatable
Comparable 继承自 Equatable,因此它需要实现 Equatable 的方法(即 == 和 !=)。
另外,它需要实现一个核心函数 static func <,并由此自动获得其余的比较运算符(<=, >, >=)。
核心方法
static func < (lhs: Self, rhs: Self) -> Bool
这是实现 Comparable 协议的唯一必需方法,其作用是:
比较两个值 lhs 和 rhs,如果 lhs 小于 rhs,返回 true;否则返回 false。
这个方法的实现方式由具体类型决定。
这是其他比较运算符(<=, >, >=)的基础,标准库会基于此方法提供其余运算符的默认实现。
示例:
struct Person: Comparable {
let age: Int
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.age < rhs.age
}
}
let person1 = Person(age: 25)
let person2 = Person(age: 30)
print(person1 < person2) // true
其他方法
static func <= (lhs: Self, rhs: Self) -> Bool
比较两个值 lhs 和 rhs,如果 lhs 小于或等于 rhs,返回 true;否则返回 false。
默认实现: 标准库会自动实现为:
!(rhs < lhs)
static func >= (lhs: Self, rhs: Self) -> Bool
比较两个值 lhs 和 rhs,如果 lhs 大于或等于 rhs,返回 true;否则返回 false。
默认实现: 标准库会自动实现为:
!(lhs < rhs)
static func > (lhs: Self, rhs: Self) -> Bool
比较两个值 lhs 和 rhs,如果 lhs 大于 rhs,返回 true;否则返回 false。
默认实现: 标准库会自动实现为:
rhs < lhs
为什么只需要实现 <?
Comparable 的设计是为了降低实现成本。开发者只需定义 <,其余操作符都能通过标准库的默认实现得到。
逻辑关系:
1、<=: lhs 小于或等于 rhs 等价于 !(rhs < lhs)。
2、>=: lhs 大于或等于 rhs 等价于 !(lhs < rhs)。
3、>: lhs 大于 rhs 等价于 rhs < lhs。
通过这些推导,所有比较操作符都能从 < 中派生。
使用场景
1、排序:
如果类型实现了 Comparable,可以直接调用 sort() 和 sorted() 方法。
let numbers = [5, 2, 8, 3]
let sortedNumbers = numbers.sorted() // [2, 3, 5, 8]
2、最大/最小值:
使用 min() 和 max() 获取集合中的最小值或最大值。
let numbers = [5, 2, 8, 3]
print(numbers.min()) // Optional(2)
print(numbers.max()) // Optional(8)
3、范围比较:
例如:
if value >= lowerBound && value <= upperBound {
print("Value is in range")
}
总结
1、Comparable 协议的核心是 < 方法,其余方法基于此方法派生。
2、用途:通过实现 Comparable,可以让类型支持排序、范围比较和集合操作等功能。
3、设计意图:降低实现成本,提高代码的通用性和可读性。
相关文章
Swift通过实现Comparable协议定义比较规则:https://fangjunyu.com/2024/11/21/swift通过实现comparable协议定义比较规则