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. 注意事项 #
- 性能:对大数据集排序时,优先使用索引属性(如
@Attribute(.unique)
标注的字段)。 - 兼容性:
SortDescriptor
在 Swift 和 Objective-C 中行为一致,但 Swift 版本支持更简洁的语法。 - 自定义逻辑:复杂排序可通过闭包实现,但需注意闭包捕获上下文的开销。
完整示例代码 #
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 标准库为常见类型(如
Int
、String
、Date
)提供了默认的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
的对比
#
特性 | SortComparator | SortDescriptor (NSSortDescriptor) |
---|---|---|
所属框架 | Swift 标准库 | Foundation 框架 |
类型安全 | 强类型(通过泛型和协议) | 弱类型(依赖字符串键路径) |
自定义能力 | 通过实现协议完全自定义逻辑 | 依赖 selector 或闭包 |
SwiftUI 集成 | 深度集成(如 @Query ) | 需转换为 NSSortDescriptor |
多条件排序 | 直接组合多个比较器 | 需手动管理数组顺序 |
性能 | 高效(编译时优化) | 略低(运行时动态派发) |
7. 最佳实践 #
优先使用
KeyPathComparator
:
对简单属性排序时,直接使用内置的KeyPathComparator
而非自定义类型。users.sorted(using: [KeyPathComparator(\.name)])
组合比较器时注意顺序:
排序条件按数组顺序优先级递减:// 先按国家,再按城市 [CountryComparator(), CityComparator()]
利用
localizedStandard
处理本地化字符串:
对用户可见的字符串排序时,优先考虑本地化规则:let comparator = StringComparator(.localizedStandard)
性能敏感场景避免复杂闭包:
对大数据集排序时,优先使用基于键路径的比较器而非闭包。
完整示例:自定义复杂排序 #
// 按用户是否是管理员 + 姓名长度排序
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 和原生集合类型的数据处理。
核心概念 #
- 键路径驱动:直接通过属性路径(如
\.age
)指定排序依据,无需手动编写闭包。 - 自动比较器生成:根据属性类型自动选择
ComparableComparator
(数值类型)或String.StandardComparator.localizedStandard
(字符串类型)。 - 多条件排序:支持组合多个
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)
}
}
}
与传统方法的对比 #
特性 | KeyPathComparator | NSSortDescriptor | 闭包排序 |
---|---|---|---|
类型安全 | ✅ 编译时检查键路径有效性 | ❌ 依赖字符串键路径 | ✅ 类型安全但冗长 |
语法简洁性 | ✅ 直接通过 \.property 指定 | ❌ 需手动拼接字符串 | ❌ 需完整闭包实现 |
多条件支持 | ✅ 组合多个比较器 | ✅ 但需手动管理数组顺序 | ✅ 但闭包逻辑复杂 |
本地化支持 | ✅ 自动处理字符串本地化 | ✅ 需显式指定选择器 | ❌ 需手动实现 |
最佳实践 #
- 优先使用键路径:替代闭包减少冗余代码。
- 利用自动比较器:对
Comparable
类型无需额外配置。 - 动态排序:在 SwiftUI 中结合
@State
或@Binding
实现交互式排序。
通过 KeyPathComparator
,开发者能以声明式语法高效实现复杂排序逻辑,同时享受类型安全和 Swift 原生框架的深度集成优势。