Swift
Predicate
Programming
iOS Development
Swift Tips

Using Predicate in Swift

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Swift, using a predicate usually means using NSPredicate with Foundation, Core Data, or another Cocoa API that expects a predicate object. It is a reusable condition that describes which objects match and which do not. The key decision is whether you are working with Objective-C-style APIs that need NSPredicate, or with pure Swift collections where a closure is usually clearer.

Use NSPredicate When the API Expects It

NSPredicate is common when filtering NSArray, NSFetchedResultsController, and Core Data fetch requests.

swift
1import Foundation
2
3let people: NSArray = [
4    ["name": "Ava", "age": 22],
5    ["name": "Liam", "age": 17],
6    ["name": "Mina", "age": 31]
7]
8
9let predicate = NSPredicate(format: "age >= %d", 18)
10let adults = people.filtered(using: predicate)
11print(adults)

This style depends on key-value coding, so it works best with Foundation objects and models that are visible to Objective-C runtime features. That is why it still appears frequently in Apple platform code even when most of the surrounding project is written in modern Swift.

Core Data Is a Natural Fit

One of the best uses for predicates is narrowing a Core Data fetch request before data is loaded into memory.

swift
1import CoreData
2
3let request: NSFetchRequest<Person> = Person.fetchRequest()
4request.predicate = NSPredicate(format: "lastName == %@", "Smith")
5request.fetchLimit = 20

You can also combine conditions:

swift
1request.predicate = NSPredicate(
2    format: "age >= %d AND city == %@",
3    18,
4    "Toronto"
5)

Filtering at the fetch layer is usually better than loading everything and filtering later in Swift. It reduces work, reduces memory use, and keeps the query intent close to the data source.

Prefer filter for Pure Swift Collections

If you are dealing with normal Swift structs and arrays, a closure is usually the better option.

swift
1struct Person {
2    let name: String
3    let age: Int
4}
5
6let people = [
7    Person(name: "Ava", age: 22),
8    Person(name: "Liam", age: 17),
9    Person(name: "Mina", age: 31)
10]
11
12let adults = people.filter { $0.age >= 18 }
13print(adults)

This is type-safe and compiler-checked. If you rename age, the compiler tells you what broke. A format-string predicate cannot do that, which is one reason it should not be the default for ordinary Swift collection filtering.

Use Compound Predicates for More Complex Rules

When a Foundation API requires a predicate and the matching logic has several parts, compose smaller predicates instead of writing one opaque string.

swift
1import Foundation
2
3let agePredicate = NSPredicate(format: "age >= %d", 18)
4let cityPredicate = NSPredicate(format: "city == %@", "Toronto")
5let combined = NSCompoundPredicate(andPredicateWithSubpredicates: [
6    agePredicate,
7    cityPredicate,
8])
9
10let people: NSArray = [
11    ["name": "Ava", "age": 22, "city": "Toronto"],
12    ["name": "Liam", "age": 17, "city": "Toronto"],
13    ["name": "Mina", "age": 31, "city": "Montreal"]
14]
15
16print(people.filtered(using: combined))

This is easier to maintain than a very long predicate string with multiple operators and substitutions.

Block-Based Predicates Are Sometimes Safer

NSPredicate also supports block-based construction, which avoids format-string syntax when the logic is easier to write in Swift.

swift
1import Foundation
2
3let evenPredicate = NSPredicate { object, _ in
4    guard let number = object as? Int else {
5        return false
6    }
7    return number % 2 == 0
8}
9
10let numbers = [1, 2, 3, 4, 5, 6] as NSArray
11print(numbers.filtered(using: evenPredicate))

This is useful when you need richer matching logic, though it is less portable to APIs that want a serializable predicate expression.

Keep the Boundary Clear

A lot of confusion around predicates comes from using the wrong tool at the wrong layer. NSPredicate is valuable because some Apple APIs are designed around it. That does not mean every collection filter in Swift should become a predicate string.

If the job is local in-memory filtering of Swift values, prefer closures. If the job is configuring a Cocoa or Core Data API that expects NSPredicate, use it directly and keep the expression as simple as possible.

Common Pitfalls

  • Using NSPredicate for ordinary Swift arrays when filter with a closure would be simpler and safer.
  • Misspelling property names inside a format string and losing compiler help.
  • Filtering after fetching large Core Data result sets instead of filtering in the fetch request.
  • Writing one very long predicate string when smaller compound predicates would be easier to maintain.
  • Mixing Swift value types and Objective-C-style predicate assumptions without checking API compatibility.

Summary

  • 'NSPredicate is the right tool when Foundation or Core Data APIs require a predicate object.'
  • For normal Swift arrays, filter is usually the clearer and safer choice.
  • Core Data predicates are most useful when they narrow results before fetch.
  • 'NSCompoundPredicate helps keep multi-part conditions readable.'
  • Choose predicates based on the API boundary, not just because the feature exists.

Course illustration
Course illustration

All Rights Reserved.