PredicateKit
🎯 PredicateKit allows Swift developers to write expressive and type-safe predicates for CoreData using key-paths, comparisons and logical operators, literal values, and functions.
Install / Use
/learn @ftchirou/PredicateKitREADME
🎯 PredicateKit
<img src="https://img.shields.io/badge/coverage-100%25-green">
<img src="https://img.shields.io/badge/platforms-iOS%2011%2B%20%7C%20macOS%2010.15%2B%20%7C%20watchOS%205%2B%20%7C%20tvOS%2011%2B-lightgrey"> <img src="https://img.shields.io/badge/swift-%3E%3D%205.1-orange">
PredicateKit is an alternative to NSPredicate allowing you to
write expressive and type-safe predicates for CoreData using key-paths,
comparisons and logical operators, literal values, and functions.
Contents
Motivation
CoreData is a formidable piece of technology, however not all of its API has caught up with the modern Swift world. Specifically, fetching and filtering objects from
CoreData relies heavily on NSPredicate and NSExpression. Unfortunately, a whole range of bugs and runtime errors can easily be introduced using those APIs.
For instance, we can compare a property of type String to a value of type Int or even use a non-existant property in a predicate; these mistakes will go un-noticed
at compile time but can cause important errors at runtime that may not be obvious to diagnose. This is where PredicateKit comes in by making it virtually impossible to
introduce these types of errors.
Concretely, PredicateKit provides
- a type-safe and expressive API for writing predicates. When using PredicateKit, all properties involved in your predicates are expressed using key-paths. This ensures that the usage of inexistant properties or typos are caught at compile time. Additionally, all operations such as comparisons, functions calls, etc. are strongly-typed, making it impossible to write invalid predicates.
- an improved developer experience. Enjoy auto-completion and syntax highlighting when writing your predicates. In addition, PredicateKit
is just a lightweight replacement for
NSPredicate, no major change to your codebase is required, no special protocol to conform to, no configuration, etc. Simplyimport PredicateKit, write your predicates and use the functionsNSManagedObjectContext.fetch(where:)orNSManagedObjectContext.count(where:)to execute them.
Installation
Carthage
Add the following line to your Cartfile.
github "ftchirou/PredicateKit" ~> 1.0.0
CocoaPods
Add the following line to your Podfile.
pod 'PredicateKit', ~> '1.0.0'
Swift Package Manager
Update the dependencies array in your Package.swift.
dependencies: [
.package(url: "https://github.com/ftchirou/PredicateKit", .upToNextMajor(from: "1.0.0"))
]
Quick start
Fetching objects
To fetch objects using PredicateKit, use the function fetch(where:) on an instance of NSManagedObjectContext passing as argument a predicate. fetch(where:) returns an object of type FetchRequest on which you call result() to execute the request and retrieve the matching objects.
Example
let notes: [Note] = try managedObjectContext
.fetch(where: \Note.text == "Hello, World!" && \Note.creationDate < Date())
.result()
You write your predicates using the key-paths of the entity to filter and a combination of comparison and logical operators, literal values, and functions calls.
See Writing predicates for more about writing predicates.
Fetching objects as dictionaries
By default, fetch(where:) returns an array of subclasses of NSManagedObject. You can specify that the objects be returned as an array of dictionaries ([[String: Any]])
simply by changing the type of the variable storing the result of the fetch.
Example
let notes: [[String: Any]] = try managedObjectContext
.fetch(where: \Note.text == "Hello, World!" && \Note.creationDate < Date())
.result()
Configuring the fetch
fetch(where:) returns an object of type FetchRequest. You can apply a series of modifiers on this object to further configure how the objects should be matched and returned.
For example, sorted(by: \Note.creationDate, .descending) is a modifier specifying that the objects should be sorted by the creation date in the descending order. A modifier returns a mutated FetchRequest; a series
of modifiers can be chained together to create the final FetchRequest.
Example
let notes: [Note] = try managedObjectContext
.fetch(where: (\Note.text).contains("Hello, World!") && \Note.creationDate < Date())
.limit(50) // Return 50 objects matching the predicate.
.offset(100) // Skip the first 100 objects matching the predicate.
.sorted(by: \Note.creationDate) // Sort the matching objects by their creation date.
.result()
See Request modifiers for more about modifiers.
Fetching objects with the @FetchRequest property wrapper
PredicateKit extends the SwiftUI @FetchRequest property wrapper to support type-safe predicates. To use, simply initialize a @FetchRequest with a predicate.
Example
import PredicateKit
import SwiftUI
struct ContentView: View {
@SwiftUI.FetchRequest(predicate: \Note.text == "Hello, World!")
var notes: FetchedResults<Note>
var body: some View {
List(notes, id: \.self) {
Text($0.text)
}
}
}
You can also initialize a @FetchRequest with a full-fledged request with modifiers and sort descriptors.
Example
import PredicateKit
import SwiftUI
struct ContentView: View {
@SwiftUI.FetchRequest(
fetchRequest: FetchRequest(predicate: (\Note.text).contains("Hello, World!"))
.limit(50)
.offset(100)
.sorted(by: \Note.creationDate)
)
var notes: FetchedResults<Note>
var body: some View {
List(notes, id: \.self) {
Text($0.text)
}
}
}
Both initializers accept an optional parameter animation that will be used to animate changes in the fetched results.
Example
import PredicateKit
import SwiftUI
struct ContentView: View {
@SwiftUI.FetchRequest(
predicate: (\Note.text).contains("Hello, World!"),
animation: .easeInOut
)
var notes: FetchedResults<Note>
var body: some View {
List(notes, id: \.self) {
Text($0.text)
}
}
}
You can update the predicate associated with your FetchedResults using updatePredicate.
Example
import PredicateKit
import SwiftUI
struct ContentView: View {
@SwiftUI.FetchRequest(predicate: \Note.text == "Hello, World!")
var notes: FetchedResults<Note>
var body: some View {
List(notes, id: \.self) {
Text($0.text)
}
Button("Show recents") {
let recentDate: Date = // ...
notes.updatePredicate(\Note.createdAt >= recentDate)
}
}
}
This will cause the associated FetchRequest to execute a fetch with the new predicate when the Show recents button is tapped.
Fetching objects with the @SectionedFetchRequest property wrapper
Predica
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
