Foundation — Filter and Sorting

Use predicates, expressions, and sort descriptors to examine elements in collections and other services.

文档参考 https://developer.apple.com/documentation/foundation/filters_and_sorting

SortDescriptor #

在 Foundation 框架中,SortDescriptor(或 NSSortDescriptor)用于定义集合数据的排序规则,支持对属性键路径、排序顺序及自定义比较逻辑的控制。以下是详细使用方法和多场景示例:


1. 基本概念 #

  • 作用:定义排序规则,用于对数组、Core Data/SwiftData 查询结果等集合进行排序。
  • 核心属性
    • keyPath:排序依据的属性路径(如 \User.name)。
    • ascending:是否升序(默认 true)。
    • comparator:自定义比较逻辑(可选)。

2. 基础用法 #

Swift 示例(SwiftData) #

// 按 name 升序排序
let descriptor = SortDescriptor<User>(\.name)

// 按 company.name 降序排序
let descriptor = SortDescriptor<User>(\.company.name, order: .reverse)

// 多条件排序:先按 company.name 升序,再按 name 降序
let descriptors = [
    SortDescriptor<User>(\.company.name),
    SortDescriptor<User>(\.name, order: .reverse)
]

Objective-C 示例(NSSortDescriptor) #

// 按 name 升序排序
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];

// 多条件排序:先按 company.name 升序,再按 name 降序
NSArray *descriptors = @[
    [NSSortDescriptor sortDescriptorWithKey:@"company.name" ascending:YES],
    [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:NO]
];

3. 使用场景与示例 #

场景 1:SwiftData 查询排序 #

// 查询 User 并按 name 排序
let descriptor = FetchDescriptor<User>(sortBy: [SortDescriptor(\.name)])
let users = try context.fetch(descriptor)

场景 2:对 Swift 数组排序 #

let users = [User(name: "张三"), User(name: "李四")]
let sortedUsers = users.sorted(using: [SortDescriptor(\.name, order: .reverse)])

场景 3:自定义比较逻辑 #

// 按字符串长度排序
let descriptor = SortDescriptor<String> { $0.count < $1.count }
let strings = ["Swift", "Foundation", "iOS"]
let sortedStrings = strings.sorted(using: [descriptor]) // ["iOS", "Swift", "Foundation"]

场景 4:多条件混合排序 #

// 先按 company.address 升序,再按 name 降序
let descriptors = [
    SortDescriptor(\.company.address),
    SortDescriptor(\.name, order: .reverse)
]
let sortedUsers = users.sorted(using: descriptors)

4. 与其他框架结合 #

Core Data 查询 #

// 使用 NSSortDescriptor(Objective-C 兼容)
let request: NSFetchRequest<User> = User.fetchRequest()
request.sortDescriptors = [
    NSSortDescriptor(key: "name", ascending: true)
]

Combine 流处理 #

// 对发布的数据流排序
usersPublisher
    .map { users in
        users.sorted(using: [SortDescriptor(\.name)])
    }
    .sink { print($0) }

5. 注意事项 #

  1. 性能:对大数据集排序时,优先使用索引属性(如 @Attribute(.unique) 标注的字段)。
  2. 兼容性SortDescriptor 在 Swift 和 Objective-C 中行为一致,但 Swift 版本支持更简洁的语法。
  3. 自定义逻辑:复杂排序可通过闭包实现,但需注意闭包捕获上下文的开销。

完整示例代码 #

SwiftData 多条件排序 #

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

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

Objective-C 多字段排序 #

NSArray *users = @[user1, user2, user3];
NSArray *sortedUsers = [users sortedArrayUsingDescriptors:@[
    [NSSortDescriptor sortDescriptorWithKey:@"company.address" ascending:YES],
    [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:NO]
]];

通过上述示例,可灵活应用 SortDescriptor 实现单字段、多字段、自定义逻辑的排序需求。

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 类型系统和性能优化的优势。

KeyPathComparator #

KeyPathComparator 是 Swift 中用于通过键路径(KeyPath)定义排序规则的现代工具,自 iOS 15 和 macOS 12 起引入 Foundation 框架。它通过类型安全的方式简化了基于属性的排序逻辑,特别适用于 SwiftUI 和原生集合类型的数据处理。


核心概念 #

  1. 键路径驱动:直接通过属性路径(如 \.age)指定排序依据,无需手动编写闭包。
  2. 自动比较器生成:根据属性类型自动选择 ComparableComparator(数值类型)或 String.StandardComparator.localizedStandard(字符串类型)。
  3. 多条件排序:支持组合多个 KeyPathComparator 实现优先级排序。

基础用法 #

示例 1:单属性排序 #

struct Person {
    let name: String
    let age: Int
}

let people = [
    Person(name: "张三", age: 25),
    Person(name: "李四", age: 30),
    Person(name: "王五", age: 20)
]

// 按 age 升序排序
let sortedByAge = people.sorted(using: KeyPathComparator(\.age))
// 结果:王五(20), 张三(25), 李四(30)

示例 2:降序与本地化字符串排序 #

// 按 name 降序(本地化规则)
let sortedByName = people.sorted(using: KeyPathComparator(\.name, order: .reverse))
// 若 name 为中文,自动应用本地化排序规则

进阶用法 #

多条件排序 #

struct Player {
    let competitorNumber: Int
    let round1: Int
    let round2: Int
}

let players = [
    Player(competitorNumber: 1, round1: 75, round2: 69),
    Player(competitorNumber: 2, round1: 31, round2: 93),
    Player(competitorNumber: 3, round1: 91, round2: 88)
]

// 先按 round1 降序,再按 round2 升序
let comparators = [
    KeyPathComparator(\.round1, order: .reverse),
    KeyPathComparator(\.round2)
]
let sortedPlayers = players.sorted(using: comparators)
// 结果:Player(round1:91, round2:88), Player(round1:75, round2:69), Player(round1:31, round2:93)

自定义排序逻辑 #

// 按年龄是否为偶数排序(自定义规则)
let customComparator = KeyPathComparator(\.age) { a, b in
    (a.isMultiple(of: 2), a) < (b.isMultiple(of: 2), b)
}
let customSorted = people.sorted(using: customComparator)
// 结果:偶数年龄优先,同奇偶性按数值升序

与 SwiftUI 集成 #

在 SwiftUI 中,@Query 和列表视图可直接使用 KeyPathComparator 实现动态排序:

struct LeaderboardView: View {
    @State private var sortOrder = [KeyPathComparator(\Player.round1, order: .reverse)]
    
    var body: some View {
        Table(players, sortOrder: $sortOrder) {
            TableColumn("选手编号", value: \.competitorNumber)
            TableColumn("第一轮", value: \.round1)
            TableColumn("第二轮", value: \.round2)
        }
        .onChange(of: sortOrder) { newOrder in
            players.sort(using: newOrder)
        }
    }
}

与传统方法的对比 #

特性KeyPathComparatorNSSortDescriptor闭包排序
类型安全✅ 编译时检查键路径有效性❌ 依赖字符串键路径✅ 类型安全但冗长
语法简洁性✅ 直接通过 \.property 指定❌ 需手动拼接字符串❌ 需完整闭包实现
多条件支持✅ 组合多个比较器✅ 但需手动管理数组顺序✅ 但闭包逻辑复杂
本地化支持✅ 自动处理字符串本地化✅ 需显式指定选择器❌ 需手动实现

最佳实践 #

  1. 优先使用键路径:替代闭包减少冗余代码。
  2. 利用自动比较器:对 Comparable 类型无需额外配置。
  3. 动态排序:在 SwiftUI 中结合 @State@Binding 实现交互式排序。

通过 KeyPathComparator,开发者能以声明式语法高效实现复杂排序逻辑,同时享受类型安全和 Swift 原生框架的深度集成优势。

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