๐พ Mastering SwiftData
A modern database replacing Core Data. Easily implement persistent storage with Swift macros.
โญ Difficulty: โญโญ
โฑ๏ธ Est. Time: 1-2h
๐ App Frameworks
โจ SwiftData?
SwiftData is a new data persistence framework available on iOS 17+. It completely replaces Core Data and is much simpler thanks to Swift macros.
๐ฆ Define Model
Todo.swift
import SwiftData import Foundation @Model final class Todo { var title: String var isCompleted: Bool var createdAt: Date init(title: String, isCompleted: Bool = false) { self.title = title self.isCompleted = isCompleted self.createdAt = Date() } }
๐๏ธ App Setup
TodoApp.swift
import SwiftUI import SwiftData @main struct TodoApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: Todo.self) } }
๐ Reading Data (@Query)
ContentView.swift
import SwiftUI import SwiftData struct ContentView: View { @Query private var todos: [Todo] @Environment(\.modelContext) private var modelContext var body: some View { NavigationStack { List { ForEach(todos) { todo in HStack { Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle") .foregroundStyle(todo.isCompleted ? .green : .gray) Text(todo.title) .strikethrough(todo.isCompleted) } .onTapGesture { todo.isCompleted.toggle() } } .onDelete(perform: deleteTodos) } .navigationTitle("ํ ์ผ") .toolbar { Button("Add", systemImage: "plus") { addTodo() } } } } func addTodo() { let newTodo = Todo(title: "์ ํ ์ผ") modelContext.insert(newTodo) } func deleteTodos(at offsets: IndexSet) { for index in offsets { modelContext.delete(todos[index]) } } }
๐ Query Filtering & Sorting
FilteredView.swift
struct FilteredTodoView: View { // Complete๋์ง ์์ ํ ์ผ๋ง, ์ต์ ์ ์ ๋ ฌ @Query( filter: #Predicate<Todo> { todo in !todo.isCompleted }, sort: \Todo.createdAt, order: .reverse ) private var incompleteTodos: [Todo] var body: some View { List(incompleteTodos) { todo in Text(todo.title) } } } // ์ฌ๋ฌ ์ ๋ ฌ ์กฐ๊ฑด @Query(sort: [ SortDescriptor(\Todo.isCompleted), SortDescriptor(\Todo.createdAt, order: .reverse) ]) private var sortedTodos: [Todo]
๐ Defining Relationships
Models.swift
import SwiftData @Model final class Category { var name: String @Relationship(deleteRule: .cascade, inverse: \Todo.category) var todos: [Todo] = [] init(name: String) { self.name = name } } @Model final class Todo { var title: String var isCompleted: Bool var category: Category? init(title: String, category: Category? = nil) { self.title = title self.isCompleted = false self.category = category } }
๐พ Manual Save & Rollback
ManualSave.swift
struct TodoEditView: View { @Environment(\.modelContext) private var modelContext @State private var title = "" var body: some View { Form { TextField("์ ๋ชฉ", text: $title) HStack { Button("Save") { save() } Button("Cancel", role: .cancel) { rollback() } } } } func save() { let todo = Todo(title: title) modelContext.insert(todo) do { try modelContext.save() } catch { print("Save failed: \(error)") } } func rollback() { modelContext.rollback() } }
๐ง Direct ModelContext Usage
FetchData.swift
import SwiftData func fetchTodos(context: ModelContext) throws -> [Todo] { let descriptor = FetchDescriptor<Todo>( predicate: #Predicate { !$0.isCompleted }, sortBy: [SortDescriptor(\Todo.createdAt, order: .reverse)] ) return try context.fetch(descriptor) } // ๊ฐ์๋ง Fetch func countTodos(context: ModelContext) throws -> Int { let descriptor = FetchDescriptor<Todo>() return try context.fetchCount(descriptor) }
๐๏ธ Delete Rules
DeleteRule.swift
@Model final class Project { var name: String // cascade: Project ์ญ์ ์ Task๋ ๋ชจ๋ ์ญ์ @Relationship(deleteRule: .cascade) var tasks: [Task] = [] // nullify: Project ์ญ์ ์ Task์ project๋ฅผ nil๋ก // @Relationship(deleteRule: .nullify) // deny: Task๊ฐ ์์ผ๋ฉด Project ์ญ์ ๋ถ๊ฐ // @Relationship(deleteRule: .deny) init(name: String) { self.name = name } } @Model final class Task { var title: String var project: Project? init(title: String, project: Project? = nil) { self.title = title self.project = project } }
๐ฏ Unique Constraints
UniqueModel.swift
import SwiftData @Model final class User { @Attribute(.unique) var email: String var name: String init(email: String, name: String) { self.email = email self.name = name } } // ๋์ผํ email๋ก insert ์ Automatically update๋จ
๐งช In-Memory Container for Testing
PreviewContainer.swift
import SwiftData extension ModelContainer { static var preview: ModelContainer { let schema = Schema([Todo.self]) let configuration = ModelConfiguration(isStoredInMemoryOnly: true) let container = try! ModelContainer(for: schema, configurations: configuration) // Sample ๋ฐ์ดํฐ ์ถ๊ฐ let context = container.mainContext context.insert(Todo(title: "ํ ์ผ 1")) context.insert(Todo(title: "ํ ์ผ 2")) return container } } // Preview์์ ์ฌ์ฉ #Preview { ContentView() .modelContainer(ModelContainer.preview) }
๐ก HIG Guidelines
โ
@Query๋ Automatically UI ์
๋ฐ์ดํธ
โ
ModelContext๋ auto save
โ
Core Data๋ณด๋ค 80% ์ ์ ์ฝ๋
โ
ํ์
์์ ์ฑ ๋ณด์ฅ
โ
Swift ๋งคํฌ๋ก๋ก ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ ๊ฑฐ