Swift通过实现Comparable协议定义比较规则
Swift通过实现Comparable协议定义比较规则

Swift通过实现Comparable协议定义比较规则

场景描述

在使用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

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注