Filter and Sorting — SortComparator

在 Swift 中,SortComparator 是一个用于定义排序规则的协议,它提供了一种更现代、灵活且类型安全的方式来实现排序逻辑。与 Foundation 的 SortDescriptor 相比,SortComparator 是 Swift 标准库的一部分,更深度集成 Swift 语言特性(如泛型、协议和闭包),适用于 Swift 原生集合(如 Array)和 SwiftUI 等框架。


1. 核心概念 #

  • 协议定义SortComparator 是一个协议,要求实现 compare(_:to:) 方法。
  • 内置实现:Swift 标准库为常见类型(如 IntStringDate)提供了默认的 SortComparator
  • 组合能力:支持通过多个 SortComparator 组合实现多条件排序。

2. 基本用法 #

直接使用内置比较器 #

let numbers = [5, 3, 1, 4, 2]
let sortedNumbers = numbers.sorted(using: [IntComparator()]) // [1, 2, 3, 4, 5]

let strings = ["Swift", "iOS", "Xcode"]
let sortedStrings = strings.sorted(using: [StringComparator()]) // ["iOS", "Swift", "Xcode"]

自定义排序顺序 #

// 降序排列
let descendingComparator = IntComparator(.reverse)
let sortedDescending = numbers.sorted(using: [descendingComparator]) // [5, 4, 3, 2, 1]

3. 自定义 SortComparator #

示例 1:按字符串长度排序 #

struct StringLengthComparator: SortComparator {
    typealias Compared = String

    var order: SortOrder = .forward // 默认升序

    func compare(_ lhs: String, _ rhs: String) -> ComparisonResult {
        if lhs.count < rhs.count {
            return order == .forward ? .orderedAscending : .orderedDescending
        } else if lhs.count > rhs.count {
            return order == .forward ? .orderedDescending : .orderedAscending
        } else {
            return .orderedSame
        }
    }
}

let strings = ["Apple", "Swift", "iOS"]
let comparator = StringLengthComparator(order: .reverse)
let sorted = strings.sorted(using: [comparator]) // ["Apple", "Swift", "iOS"]

示例 2:按自定义模型属性排序 #

struct User {
    var name: String
    var age: Int
}

struct UserAgeComparator: SortComparator {
    typealias Compared = User

    var order: SortOrder = .forward

    func compare(_ lhs: User, _ rhs: User) -> ComparisonResult {
        if lhs.age < rhs.age {
            return order == .forward ? .orderedAscending : .orderedDescending
        } else if lhs.age > rhs.age {
            return order == .forward ? .orderedDescending : .orderedAscending
        } else {
            return .orderedSame
        }
    }
}

let users = [
    User(name: "张三", age: 25),
    User(name: "李四", age: 30),
    User(name: "王五", age: 20)
]
let sortedUsers = users.sorted(using: [UserAgeComparator()])
// 结果按 age 升序排列:王五(20), 张三(25), 李四(30)

4. 多条件排序 #

组合多个比较器 #

// 先按年龄降序,再按姓名升序
let comparators: [any SortComparator] = [
    UserAgeComparator(order: .reverse),
    KeyPathComparator(\.name, order: .forward)
]

let sortedUsers = users.sorted(using: comparators)
// 李四(30), 张三(25), 王五(20)

使用 KeyPathComparator #

Swift 标准库提供了 KeyPathComparator,可直接通过键路径生成比较器:

// 按 name 升序
let nameComparator = KeyPathComparator(\.name)

// 按 age 降序
let ageComparator = KeyPathComparator(\.age, order: .reverse)

let sortedUsers = users.sorted(using: [ageComparator, nameComparator])

5. 在 SwiftUI 中的应用 #

@Query 结合(SwiftData) #

struct ContentView: View {
    @Query(sort: [
        KeyPathComparator(\.age, order: .reverse),
        KeyPathComparator(\.name)
    ]) var users: [User]

    var body: some View {
        List(users) { user in
            Text("\(user.name) - \(user.age)")
        }
    }
}

动态切换排序条件 #

enum SortOption {
    case name, age
}

struct UserListView: View {
    @State private var sortOption: SortOption = .name
    @State private var sortOrder: SortOrder = .forward

    var body: some View {
        let comparator: any SortComparator = switch sortOption {
        case .name: KeyPathComparator(\.name, order: sortOrder)
        case .age: KeyPathComparator(\.age, order: sortOrder)
        }

        List(users.sorted(using: [comparator])) { user in
            // ...
        }
        .toolbar {
            Picker("Sort By", selection: $sortOption) {
                Text("Name").tag(SortOption.name)
                Text("Age").tag(SortOption.age)
            }
            Button(sortOrder == .forward ? "↑" : "↓") {
                sortOrder.toggle()
            }
        }
    }
}

6. 与 SortDescriptor 的对比 #

特性SortComparatorSortDescriptor (NSSortDescriptor)
所属框架Swift 标准库Foundation 框架
类型安全强类型(通过泛型和协议)弱类型(依赖字符串键路径)
自定义能力通过实现协议完全自定义逻辑依赖 selector 或闭包
SwiftUI 集成深度集成(如 @Query需转换为 NSSortDescriptor
多条件排序直接组合多个比较器需手动管理数组顺序
性能高效(编译时优化)略低(运行时动态派发)

7. 最佳实践 #

  1. 优先使用 KeyPathComparator
    对简单属性排序时,直接使用内置的 KeyPathComparator 而非自定义类型。

    users.sorted(using: [KeyPathComparator(\.name)])
    
  2. 组合比较器时注意顺序
    排序条件按数组顺序优先级递减:

    // 先按国家,再按城市
    [CountryComparator(), CityComparator()]
    
  3. 利用 localizedStandard 处理本地化字符串
    对用户可见的字符串排序时,优先考虑本地化规则:

    let comparator = StringComparator(.localizedStandard)
    
  4. 性能敏感场景避免复杂闭包
    对大数据集排序时,优先使用基于键路径的比较器而非闭包。


完整示例:自定义复杂排序 #

// 按用户是否是管理员 + 姓名长度排序
struct CustomUserComparator: SortComparator {
    typealias Compared = User

    var order: SortOrder = .forward

    func compare(_ lhs: User, _ rhs: User) -> ComparisonResult {
        // 管理员优先
        if lhs.isAdmin && !rhs.isAdmin {
            return order == .forward ? .orderedAscending : .orderedDescending
        } else if !lhs.isAdmin && rhs.isAdmin {
            return order == .forward ? .orderedDescending : .orderedAscending
        }

        // 非管理员按姓名长度排序
        if lhs.name.count < rhs.name.count {
            return order == .forward ? .orderedAscending : .orderedDescending
        } else if lhs.name.count > rhs.name.count {
            return order == .forward ? .orderedDescending : .orderedAscending
        }

        return .orderedSame
    }
}

// 使用示例
let users = [
    User(name: "Alice", isAdmin: false),
    User(name: "Bob", isAdmin: true),
    User(name: "Admin", isAdmin: true)
]

let sorted = users.sorted(using: [CustomUserComparator()])
// 结果:Bob (Admin, 3字母), Admin (Admin, 5字母), Alice (非Admin)

通过 SortComparator,开发者可以构建高度定制化的排序逻辑,同时享受 Swift 类型系统和性能优化的优势。

本文共 1462 字,创建于 Feb 22, 2025
相关标签: Xcode, Foundation