Aula 19 - Persistência Local com SwiftData e Testes Unitários 💾
Objetivo Pedagógico
Objetivo: Implementar persistência de dados local com SwiftData utilizando a macro @Model, relacionamentos complexos, migrações de esquema e suíte de testes unitários com containers em memória.
📑 1. Fundamentos Teóricos & Análise Técnica
A Apple lançou o SwiftData para substituir a complexidade legada do Core Data, oferecendo uma API nativa em Swift focada em segurança de tipos, concorrência moderna e integração perfeita com SwiftUI.
A estrutura do SwiftData opera através de conceitos elegantes e expressivos: 1. A Macro @Model: - Converte diretamente qualquer classe Swift convencional em um esquema de dados relacional persistível, dispensando editores visuais de modelos .xcdatamodeld. - Gera automaticamente metadados para controle de integridade, transformando propriedades em colunas e mapeando relacionamentos (@Relationship) com regras de deleção em cascata (Cascade Delete). 2. ModelContainer e ModelContext: - ModelContainer: A espinha dorsal que gerencia o armazenamento físico no disco (usando SQLite sob o capô) e o controle de versões de esquema. - ModelContext: O espaço de trabalho (Scratchpad) onde objetos são criados, modificados e removidos antes da confirmação atômica no banco. 3. Estratégia de Testes Unitários com Containers em Memória: - Para garantir testes unitários rápidos e isolados, cria-se uma configuração de ModelContainer configurada com isStoredInMemoryOnly: true. Isso permite instanciar e destruir bancos SQLite na memória RAM sem deixar resquícios no disco do simulador.
📐 Arquitetura Conceitual & Diagrama de Fluxo
graph TD
View["SwiftUI View (@Query)"] --> Context["ModelContext (Scratchpad em Memória)"]
Context --> Container["ModelContainer (Schema & Config)"]
Container --> SQLite["Armazenamento SQLite em Disco"]
TestRunner["XCTest Suite"] --> TestContainer["ModelContainer (inMemoryOnly: true)"]
TestContainer --> TestContext["ModelContext Isolado"]
style View fill:#e1f5fe,stroke:#01579b
style Context fill:#fff3e0,stroke:#e65100
style Container fill:#f3e5f5,stroke:#7b1fa2
style SQLite fill:#e8f5e9,stroke:#2e7d32
style TestRunner fill:#ffebee,stroke:#c62828 🔍 Pilares e Diretrizes Técnicas
Nesta unidade, aprofundamos os seguintes conceitos fundamentais: - Macro Declarativa @Model: Elimina o código de mapeamento relacional transformando classes puras em esquemas SQLite tipados. - Propriedade @Query no SwiftUI: Alimenta automaticamente a interface do usuário mantendo-a atualizada sempre que há inserções no banco. - ModelContainer em Memória para Testes: Permite rodar centenas de testes unitários em paralelo sem contaminação de estado persistido. - Relacionamentos Fortemente Tipados: Declaração de cardinalidade 1-N e N-N com verificação estrita pelo compilador.
🛠️ 2. Implementação Prática em Persistência Nativa Apple, SwiftData e XCTest
Abaixo está a implementação técnica de referência, estruturada com padrões de engenharia de software e foco em robustez:
// AccountSwiftDataTests.swift (Modelo SwiftData e Suíte de Testes Unitários Isolados)
import XCTest
import SwiftData
@testable import SeuApp
@Model
final class UserAccount {
@Attribute(.unique) var email: String
var username: String
var balance: Double
init(email: String, username: String, balance: Double = 0.0) {
self.email = email
self.username = username
self.balance = balance
}
}
final class UserAccountTests: XCTestCase {
var container: ModelContainer!
var context: ModelContext!
@MainActor
override func setUp() {
super.setUp()
let config = ModelConfiguration(isStoredInMemoryOnly: true)
container = try! ModelContainer(for: UserAccount.self, configurations: config)
context = container.mainContext
}
@MainActor
func test_insercaoConta_devePersistirComSucesso() throws {
let novaConta = UserAccount(email: "dev@apple.com", username: "AppleDev", balance: 500.0)
context.insert(novaConta)
try context.save()
let descriptor = FetchDescriptor<UserAccount>(predicate: #Predicate { $0.email == "dev@apple.com" })
let resultados = try context.fetch(descriptor)
XCTAssertEqual(resultados.count, 1)
XCTAssertEqual(resultados.first?.username, "AppleDev")
XCTAssertEqual(resultados.first?.balance, 500.0)
}
}
💡 Análise Passo a Passo do Código
- Configuração
isStoredInMemoryOnly: true: Executa a suíte de persistência integralmente na memória RAM, garantindo velocidade extrema e determinismo. - Predicado Tipado
#Predicate: Macro que valida condições lógicas de consulta SQL em tempo de compilação. - Anotação
@Attribute(.unique): Impõe restrição de unicidade na coluna diretamente a nível de banco relacional.
🎯 3. Próximos Passos & Sequência Didática
-
Slides da Aula
-
Quiz de Fixação
-
Exercícios Práticos
-
Desafio de Projeto