código

23 abr, 2026

Um Segredo do #Predicate em Swift: Por Que Uma Linha Muda Tudo?

Publicidade

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
}

jj

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 companyId e locationId sã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.