场景描述
在使用Swift的过程中,可能会对数据进行排序:
struct ContentView: View {
let values = [1, 5, 3, 6, 2, 9].sorted()
var body: some View {
List(values, id: \.self) {
Text(String($0))
}
}
}
通过sorted()方法可以实现对数组的排序。
但是,如果想要比较两个名称、姓氏,则无法利用简单的sorted()方法实现效果。
struct User: Identifiable {
let id = UUID()
var firstName: String
var lastName: String
}
struct ContentView: View {
let users = [
User(firstName: "Arnold", lastName: "Rimmer"),
User(firstName: "Kristine", lastName: "Kochanski"),
User(firstName: "David", lastName: "Lister"),
] .sorted()
var body: some View {
List(users) { user in
Text("\(user.lastName), \(user.firstName)")
}
}
}
Xcode输出如下报错:
Referencing instance method 'sorted()' on 'Sequence' requires that 'User' conform to 'Comparable'
Generic parameter 'Data' could not be inferred
解决方案
解决方案一
可以通过提供一个sorted()闭包来实现排序效果。
let users = [
User(firstName: "Arnold", lastName: "Rimmer"),
User(firstName: "Kristine", lastName: "Kochanski"),
User(firstName: "David", lastName: "Lister"),
].sorted {
$0.lastName < $1.lastName
}
sorted解析
sorted 的闭包会依次将数组中的两个元素传递给闭包,以确定它们的排序顺序。
$0 是当前被比较的第一个 User 对象。
$1 是当前被比较的第二个 User 对象。
比较规则是:如果 $0.lastName 小于 $1.lastName,则 $0 会排在 $1 qian4面。
解决方案一带来的问题
数据模型与视图逻辑的分离
数据模型 (User 和它的属性) 是用于存储和处理应用程序数据的结构,与界面布局无关。
视图逻辑(例如 SwiftUI 布局代码)负责显示数据模型的内容,但不应该直接包含数据模型的核心逻辑。
如果我们在视图代码中直接操作 User 的排序逻辑(例如用 .sorted 闭包排序),就把视图和模型耦合在一起了:
模型层次会被污染,难以维护。
如果其他地方需要类似的排序逻辑,就会导致代码重复。
代码可重用性问题
如果排序逻辑写在视图代码中,并且需要在多个地方使用,我们可能会重复使用类似的闭包。
这种重复代码(例如多次使用 .sorted { $0.lastName < $1.lastName })会导致维护问题:
一旦逻辑需要调整(比如加入 firstName 的比较逻辑),就需要在所有地方修改,这很容易出错。
解决方案二
User实现Comparable协议定义一个比较规则,视图中通过sorted()方法进一步实现。
struct User: Identifiable, Comparable{
let id = UUID()
var firstName: String
var lastName: String
static func <(lhs: User, rhs: User) -> Bool {
lhs.lastName > rhs.lastName
}
}
struct ContentView: View {
let users = [
User(firstName: "Arnold", lastName: "Rimmer"),
User(firstName: "Kristine", lastName: "Kochanski"),
User(firstName: "David", lastName: "Lister"),
].sorted()
var body: some View {
List(users) { user in
Text("\(user.lastName), \(user.firstName)")
}
}
}
代码解析
static func <(lhs: User, rhs: User) -> Bool
这是 Comparable 协议要求实现的一个方法,用于定义两个对象之间的“小于”关系 (<)。
参数:
lhs: 表示“左侧对象”(左操作数)。
rhs: 表示“右侧对象”(右操作数)。
返回值:返回 true 时,表示 lhs 的优先级低于 rhs。
lhs.lastName > rhs.lastName
比较 User 的 lastName 属性,采用了降序排序逻辑(>)。
如果 lhs.lastName 的字典序大于 rhs.lastName,就返回 true。
换句话说,字母序靠后的姓氏会排在前面。
使用场景
实现了 Comparable 的类型可以直接使用排序方法或比较运算符,例如:
let user1 = User(firstName: "Alice", lastName: "Brown")
let user2 = User(firstName: "Bob", lastName: "Smith")
if user1 < user2 {
print("\(user1.lastName) should appear after \(user2.lastName) in descending order.")
}
// 输出: "Brown should appear after Smith in descending order."
这段代码的比较逻辑定义了 User 的默认排序方式:按 lastName 的降序排列。通过实现 Comparable 协议,可以让 User 对象直接参与排序和比较,使代码更加简洁和灵活。
解决方案三
定义一个sortedByLastNameAndFirstName方法,用于对一个 User 的数组按照 lastName 和 firstName 排序。
struct User: Identifiable{
let id = UUID()
var firstName: String
var lastName: String
static func sortedByLastNameAndFirstName(_ users: [User]) -> [User] {
users.sorted {
if $0.lastName == $1.lastName {
return $0.firstName < $1.firstName
} else {
return $0.lastName < $1.lastName
}
}
}
}
struct ContentView: View {
let users = [
User(firstName: "Arnold", lastName: "Rimmer"),
User(firstName: "Kristine", lastName: "Kochanski"),
User(firstName: "David", lastName: "Lister"),
]
var body: some View {
List(User.sortedByLastNameAndFirstName(users)) { user in
Text("\(user.lastName), \(user.firstName)")
}
}
}
作为解决方案二的拓展,将sorted放到User结构体中,静态方法 sortedByLastNameAndFirstName接收一个 User 数组作为参数,并返回排序后的新数组。
代码解析
static func sortedByLastNameAndFirstName(_ users: [User]) -> [User] {
users.sorted {
if $0.lastName == $1.lastName {
return $0.firstName < $1.firstName
} else {
return $0.lastName < $1.lastName
}
}
}
排序逻辑
使用了 Swift 的 sorted(by:) 方法,这个方法需要一个闭包来定义比较逻辑。
排序规则如下:
如果 lastName 相同,则比较 firstName。
如果 lastName 不同,则直接按 lastName 排序。
实现细节
if $0.lastName == $1.lastName:如果两个用户的 lastName 相同,则按 firstName 比较。
return $0.firstName < $1.firstName:按 firstName 的字典序升序排列。
否则,return $0.lastName < $1.lastName:按 lastName 的字典序升序排列。 视图显示
List(User.sortedByLastNameAndFirstName(users)) { user in
Text("\(user.lastName), \(user.firstName)")
}
最后,在View视图中,将sortedByLastNameAndFirstName方法返回的users数组展示出来。
实际上解决了“解决方案一带来的问题”,但没有“解决方案二”更接近灵活。
参考文章
Adding conformance to Comparable for custom types:https://www.hackingwithswift.com/books/ios-swiftui/adding-conformance-to-comparable-for-custom-types