Um “predicate” é uma condição booleana usada para selecionar ou filtrar registros. Ele responde “este objeto atende ao critério?” e retorna verdadeiro ou falso.
Em bancos/ORMs, o predicate é enviado para o mecanismo de persistência, que retorna apenas os registros que satisfazem a condição.
Já no ecossistema da Apple temos:
NSPredicate: API clássica (Core Data, Foundation) baseada em strings formatadas, como `NSPredicate(format: “age >= %d”, 18)` . É flexível, mas menos segura (erros só aparecem em runtime).#Predicate: DSL moderno e tipado (SwiftData/SwiftUI). Você escreve condições com código Swift e o macro gera uma expressão booleana segura em tempo de compilação.
Uso em SwiftData/SwiftUI
@Query(filter: #Predicate<Model> { … }) , o $0 representa um registro do tipo Model . Você compara campos com valores capturados:
// Exemplo com strings:
#Predicate<CompanyAndLocation> {
$0.company.name == companyName && $0.location.location == locationName
}
// Exemplo por identidade:
#Predicate<CompanyAndLocation> {
$0.company.id == companyId && $0.location.id == locationId
}
Isso permite que o filtro rode no store, retornando apenas os itens que batem com o critério.
Quando usar o #Predicate?
- Filtrar uma coleção persistida via @Query ou FetchDescriptor para trazer do banco somente o que importa.
- Substitui NSPredicate quando você quer segurança de tipos e integração direta com SwiftData.
Quais são as vantagens do #Predicate?
- Tipado e checado em compile-time.
- Integra-se ao @Query e FetchDescriptor , reduzindo erros de formatação.
- Melhora completions e refatorações (se você renomear um campo, o compilador ajuda).
Cuidado com algumas pequenas armadilhas!?
- Comparar “key path com key path” em vez de “campo com valor” causa erros de tipo no DSL.
- Referenciar self ou propriedades de @Model dentro do bloco sem capturar valores simples pode quebrar a expansão do macro.
- Comparar relacionamentos por objeto inteiro pode falhar dependendo da versão do SDK; comparar por id ou por atributos únicos é mais robusto.
Show me the code:
// PredicateApp.swift
import SwiftUI
import SwiftData
@main
struct PredicateApp: App {
var body: some Scene {
WindowGroup {
DataView(
company: Company(name: "Company 1", type: 1),
location: Location(location: "Location 1", value: 100)
)
}
}
}
// CompanyAndLocation.swift
import SwiftData
@Model
class Company {
#Unique<Company>([\.name])
var name: String
var type: Int
init(name: String, type: Int) {
self.name = name
self.type = type
}
}
@Model
class Location {
#Unique<Location>([\.location])
var location: String
var value: Int
init(location: String, value: Int) {
self.location = location
self.value = value
}
}
@Model
class CompanyAndLocation {
#Unique<CompanyAndLocation>([\.company, \.location])
var company: Company
var location: Location
init(company: Company, location: Location) {
self.company = company
self.location = location
}
}
// DataView.swift
import SwiftUI
import SwiftData
struct DataView: View {
@Environment(\.modelContext) var modelContext
var company: Company
var location: Location
@Query var companyAndLocation: [CompanyAndLocation]
var body: some View {
Text("Hello World!")
}
init(company: Company, location: Location) {
self.company = company
self.location = location
let predicate = #Predicate<CompanyAndLocation>
{
$0.company == self.company && $0.location == self.location
}
_companyAndLocation = Query(filter: predicate)
}
}
Crie um projeto SwiftUI, adicione estes 3 arquivos e compile o projeto. E se você este erro significa que está tudo certo (em receber o erro).
Cannot convert value of type 'PredicateExpressions.Conjunction<PredicateExpressions.
Equal<PredicateExpressions.KeyPath<PredicateExpressions.Variable<CompanyAndLocation>, Company>,
PredicateExpressions.KeyPath<PredicateExpressions.Value<DataView>, Company>>,
PredicateExpressions.Equal<PredicateExpressions.KeyPath<PredicateExpressions.Variable<CompanyAndLocation>,
Location>, PredicateExpressions.KeyPath<PredicateExpressions.Value<DataView>,
Location>>>' to closure result type 'any StandardPredicateExpression<Bool>'
Esse erro indica que estamos tentando comparar objetos complexos diretamente, quando o #Predicate só aceita comparações de tipos escalares (Int, String, UUID, etc.). Também o erro indica que o fechamento de Predicate está retornando um tipo interno do DSL (Conjunction<Equal<…>> ) que não está sendo aceito como any StandardPredicateExpression, normalmente por construção incorreta do predicate (mistura de variável x valor ou uso de key paths em vez de valores).
O problema está nesta parte do código:
let predicate = #Predicate<CompanyAndLocation>
{
$0.company == self.company && $0.location == self.location
}
Poderíamos resolver assim:
let companyName = company.name
let locationName = location.location
let predicate = #Predicate<CompanyAndLocation>
{
$0.company.name == companyName && $0.location.location == locationName
}
Se eu não precisasse comparar o objeto completo. Mas e se eu precisasse?Neste caso para 2 propriedades seria simples, mas se este objeto tivesse mais propriedades? E se esse objeto fosse atualizado com mais propriedades com o decorrer do tempo? Então eu teria que voltar nesse código para atualizá-lo e garantir uma comparação completa.
Podemos tentar assim:
let predicate = #Predicate<CompanyAndLocation> {
$0.company.id == company.id && $0.location.id == location.id
}
Aqui estamos tentando acessar company.id e location.id diretamente. O macro não consegue rastrear essas propriedades dinâmicas de objetos externos. Ele espera valores simples e escalares que possam ser “capturados” como constantes. Ou seja, não funciona.
A solução: Compare os identificadores, não os objetos:
Essa é uma questão comum com Swift’s #Predicate macro. O problema está em como o macro captura variáveis. O #Predicate funciona através de type-safe query generation, ou seja, precisa traduzir sua expressão Swift para uma query que pode ser executada no banco de dados (CoreData ou SwiftData).
Então resolvemos assim:
let companyId = company.id
let locationId = location.id
let predicate = #Predicate<CompanyAndLocation>
{
$0.company.id == companyId && $0.location.id == locationId
}
- As variáveis
companyIdelocationIdsão capturadas pelo closure como valores simples (Equatable) - O macro consegue identificá-las como constantes externas e substituí-las corretamente na query
Conclusão:
O #Predicate só consegue traduzir para queries de banco de dados expressões que comparam valores escalares (números, strings, UUIDs). Comparações entre objetos complexos não podem ser traduzidas para SQL/query de dados.
Então dentro de um #Predicate, compare os identificadores e não os objetos, sempre extraia os valores que você quer comparar em variáveis locais simples antes de usá-las no closure. Isso permite que o macro entenda exatamente quais constantes devem ser “compiladas” na query.
If you’ve enjoyed this article, subscribe and get an email as soon as I publish!
Happy Coding!




