Fetch Descriptors
FetchDescriptor configures how data is retrieved from the database, including filtering, sorting, and pagination.
Basic Usage
// Fetch all users
let descriptor = FetchDescriptor<User>()
let users = try context.fetch(descriptor)
// Or use the static property
let users = try context.fetch(FetchDescriptor<User>.all)
Filtering
// Initialize with predicate
let adults = #Predicate<User> { $0.age >= 18 }
let descriptor = FetchDescriptor<User>(predicate: adults)
// Or use builder pattern
let descriptor = FetchDescriptor<User>()
.filter(adults)
Sorting
// Initialize with sort descriptors
let descriptor = FetchDescriptor<User>(
sortBy: [SortDescriptor(propertyName: "name")]
)
// Or use builder pattern
let descriptor = FetchDescriptor<User>()
.sorted(by: SortDescriptor(propertyName: "name"))
// Multiple sort criteria
let descriptor = FetchDescriptor<User>()
.sorted(by: SortDescriptor(propertyName: "lastName"))
.sorted(by: SortDescriptor(propertyName: "firstName"))
Pagination
// Manual limit and offset
let descriptor = FetchDescriptor<User>()
.limit(20)
.offset(40) // Skip first 40
// Using pagination helper (1-indexed pages)
let page3 = FetchDescriptor<User>.all
.paginated(page: 3, pageSize: 20) // Items 41-60
Partial Fetching
Fetch only specific properties for better performance:
// Only fetch name and email columns
let descriptor = FetchDescriptor<User>()
.fetching(\User.name, \User.email)
let users = try context.fetch(descriptor)
// user.name and user.email are populated
// Other properties have default values
Tip: Use partial fetching when displaying lists where you don't need all properties. This reduces data transfer and memory usage.
Relationship Prefetching
Eagerly load relationships to avoid N+1 queries:
// Fetch authors with their books in a single query
let descriptor = FetchDescriptor<Author>()
.prefetching(\Author.books)
let authors = try context.fetch(descriptor)
for author in authors {
// Books are already loaded - no additional query
print("\(author.name): \(author.books.count) books")
}
Including Pending Changes
Control whether unsaved changes are included in results:
// Include unsaved insertions/modifications (default: true)
let descriptor = FetchDescriptor<User>()
.includingPendingChanges(true)
// Only fetch from database, ignore pending changes
let descriptor = FetchDescriptor<User>()
.includingPendingChanges(false)
Complete Example
// Complex query combining all features
let activeAdults = #Predicate<User> { user in
user.age >= 18 && user.isActive == true
}
let descriptor = FetchDescriptor<User>()
.filter(activeAdults)
.sorted(by: SortDescriptor(propertyName: "createdAt", order: .reverse))
.fetching(\User.name, \User.email, \User.createdAt)
.paginated(page: 1, pageSize: 50)
let users = try context.fetch(descriptor)
Counting Results
// Count matching records without fetching them
let adults = #Predicate<User> { $0.age >= 18 }
let count = try context.fetchCount(
FetchDescriptor<User>(predicate: adults)
)
Fetching Identifiers
// Fetch only IDs for lightweight operations
let ids = try context.fetchIdentifiers(
FetchDescriptor<User>()
)
// Later, fetch full object when needed
let user: User? = try context.model(for: ids.first!)
Properties Summary
| Property/Method | Description |
|---|---|
.filter(_:) |
Add a predicate filter |
.sorted(by:) |
Add sort criteria |
.limit(_:) |
Maximum number of results |
.offset(_:) |
Number of results to skip |
.paginated(page:pageSize:) |
Convenience for pagination |
.fetching(_:) |
Partial fetch (specific properties) |
.prefetching(_:) |
Eager load relationships |
.includingPendingChanges(_:) |
Include unsaved changes |
Next Steps
- Sorting - More on sort descriptors
- Predicates - Advanced filtering