SwiftData 완벽 가이드 — SwiftUI 데이터베이스 저장, CRUD, 검색과 마이그레이션 구현
iPhone 앱을 개발하다 보면 사용자 데이터를 기기에 저장해야 하는 경우가 많습니다.
간단한 설정값이라면 UserDefaults로 충분하지만, 메모나 일정, 즐겨찾기, 관찰 기록처럼 데이터가 지속적으로 증가하는 앱에서는 데이터베이스가 필요합니다.
Android에서는 SQLite나 Room을 많이 사용합니다. iOS에서는 Core Data, SQLite 등을 사용할 수 있으며, iOS 17부터는 Apple의 SwiftData 프레임워크도 사용할 수 있습니다.
SwiftData는 Swift의 모델 타입을 기반으로 데이터를 저장하고 조회할 수 있도록 설계된 영속성 프레임워크입니다.
이번 글에서는 기존 SwiftUI 메모 앱을 SwiftData로 변경하면서 다음 기능을 구현하겠습니다.
SwiftData 프로젝트 설정
@Model데이터 모델 생성ModelContainer와ModelContext이해메모 추가·조회·수정·삭제(CRUD)
@Query를 이용한 목록 자동 갱신검색과 정렬
즐겨찾기 관리
MVVM과 Repository 설계
UserDefaults 데이터 이전
스키마 변경과 마이그레이션
데이터 저장 오류 및 성능 관리
개발 환경
Xcode
Swift 5.9 이상
SwiftUI
SwiftData
iOS 17 이상
Observation 프레임워크
MVVM
예제는 iOS 17 이상을 대상으로 하며, SDK 버전에 따라 일부 API의 사용 방법이 달라질 수 있습니다.
1. SwiftData란?
SwiftData는 Apple이 제공하는 데이터 영속성 프레임워크입니다.
Swift 모델을 정의하고 해당 모델을 앱 내부에 저장할 수 있습니다.
예를 들어 메모 앱에서는 다음과 같은 데이터를 관리할 수 있습니다.
| 속성 | 데이터 |
|---|---|
| id | 메모 고유 ID |
| title | 제목 |
| content | 내용 |
| isFavorite | 즐겨찾기 |
| createdAt | 생성 날짜 |
| updatedAt | 수정 날짜 |
기존 UserDefaults 방식에서는 메모 배열 전체를 JSON으로 인코딩해 저장했습니다.
반면 SwiftData에서는 개별 모델 객체를 추가하고 조회하거나 수정할 수 있습니다.
2. Android SQLite·Room과 SwiftData 비교
| 구분 | SQLite 직접 사용 | Room | SwiftData |
|---|---|---|---|
| 플랫폼 | Android 등 | Android | Apple 플랫폼 |
| 모델 정의 | 직접 테이블 설계 | @Entity | @Model |
| 데이터 조회 | SQL | DAO / SQL | FetchDescriptor / @Query |
| 데이터 저장 | INSERT / UPDATE | DAO | ModelContext |
| 관계 관리 | JOIN 등 | @Relation 등 | @Relationship |
| 마이그레이션 | 직접 관리 | Migration | SchemaMigrationPlan |
| UI 연동 | 별도 구현 | Flow 등 | @Query 등 |
SwiftData는 Room과 개념적으로 비슷한 부분이 있습니다.
하지만 Room의 DAO와 SwiftData의 ModelContext는 동일한 구조가 아닙니다.
특히 SwiftData에서는 모델 객체의 변경을 추적해 저장하는 방식에 익숙해질 필요가 있습니다.
SwiftData 프로젝트 설정
3. 별도 라이브러리 설치가 필요할까?
SwiftData는 Apple SDK에 포함된 프레임워크입니다.
따라서 일반적으로 별도의 외부 라이브러리를 설치할 필요가 없습니다.
Swift 파일에서 다음과 같이 가져옵니다.
import SwiftDataSwiftData를 사용하려면 앱의 최소 지원 버전이 해당 프레임워크를 지원해야 합니다.
이번 예제에서는 iOS 17 이상으로 설정합니다.
@Model 데이터 모델 만들기
4. Memo 모델 정의
기존 메모 앱에서 사용했던 struct Memo를 SwiftData 모델로 변경하겠습니다.
Memo.swift
import Foundation
import SwiftData
@Model
final class Memo {
@Attribute(.unique)
var id: UUID
var title: String
var content: String
var isFavorite: Bool
var createdAt: Date
var updatedAt: Date
init(
id: UUID = UUID(),
title: String,
content: String = "",
isFavorite: Bool = false,
createdAt: Date = .now,
updatedAt: Date = .now
) {
self.id = id
self.title = title
self.content = content
self.isFavorite = isFavorite
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}여기서 중요한 부분은 @Model입니다.
@Model을 사용하면 SwiftData가 해당 클래스를 영속성 모델로 관리할 수 있습니다.
또한 @Attribute(.unique)는 고유성을 지정하는 데 사용합니다.
실제 서비스에서는 고유성 제약과 충돌 처리 정책을 함께 검토해야 합니다.
5. 왜 struct가 아니라 class일까?
일반적인 Swift 데이터 모델은 struct로 작성하는 경우가 많습니다.
하지만 SwiftData의 @Model은 클래스를 대상으로 사용합니다.
@Model
final class Memo {
// ...
}SwiftData는 모델 인스턴스의 변경을 추적합니다.
따라서 단순한 값 타입 모델과는 다른 방식으로 동작합니다.
Android의 Room Entity와 개념적으로 비교할 수 있지만 객체의 상태 추적 방식은 다릅니다.
ModelContainer 이해하기
6. ModelContainer란?
ModelContainer는 SwiftData 모델을 관리하기 위한 영속성 컨테이너입니다.
앱에서 어떤 모델을 사용할지 정의하고 저장소 구성을 담당합니다.
SwiftUI에서는 .modelContainer() Modifier를 사용해 컨테이너를 연결할 수 있습니다.
7. 앱 진입점 설정
SimpleMemoApp.swift
import SwiftUI
import SwiftData
@main
struct SimpleMemoApp: App {
var body: some Scene {
WindowGroup {
MainTabView()
}
.modelContainer(for: Memo.self)
}
}이제 MainTabView와 하위 화면에서 SwiftData 환경을 사용할 수 있습니다.
여러 모델을 사용하는 경우에는 모델 목록을 함께 등록할 수 있습니다.
ModelContext 이해하기
8. ModelContext란?
ModelContext는 SwiftData 모델을 조회하고 변경하는 작업을 담당합니다.
대표적으로 다음 작업에 사용됩니다.
데이터 추가
데이터 조회
데이터 삭제
변경 사항 저장
SwiftUI View에서는 환경을 통해 가져올 수 있습니다.
@Environment(\.modelContext)
private var modelContextAndroid의 SQLiteDatabase나 Room DAO와 완전히 같지는 않지만, 데이터를 다루는 실행 컨텍스트라고 이해하면 됩니다.
CREATE — 메모 추가
9. 데이터 추가하기
SwiftData에서는 insert()를 이용해 모델을 추가할 수 있습니다.
func addMemo(
title: String,
content: String,
context: ModelContext
) throws {
let memo = Memo(
title: title,
content: content
)
context.insert(memo)
try context.save()
}SQLite에서는 INSERT 문을 작성하지만, SwiftData에서는 모델 인스턴스를 생성해 컨텍스트에 추가합니다.
save()를 호출하면 변경 사항을 영속 저장하도록 요청합니다.
READ — 메모 조회
10. FetchDescriptor 사용
SwiftData에서 데이터를 직접 조회할 때는 FetchDescriptor를 사용할 수 있습니다.
func getAllMemos(
context: ModelContext
) throws -> [Memo] {
let descriptor = FetchDescriptor<Memo>(
sortBy: [
SortDescriptor(
\Memo.createdAt,
order: .reverse
)
]
)
return try context.fetch(descriptor)
}이 코드는 생성 날짜가 최신인 메모부터 조회합니다.
Android SQLite의 다음 SQL과 목적이 비슷합니다.
SELECT *
FROM memos
ORDER BY created_at DESC;@Query 사용
11. SwiftUI에서 자동 갱신되는 목록
SwiftUI와 SwiftData를 함께 사용할 때 유용한 기능이 @Query입니다.
@Query(
sort: \Memo.createdAt,
order: .reverse
)
private var memos: [Memo]@Query를 사용하면 SwiftData의 변경 사항이 SwiftUI 목록에 반영되도록 구성할 수 있습니다.
예를 들어 다음과 같습니다.
import SwiftUI
import SwiftData
struct MemoListView: View {
@Query(
sort: \Memo.createdAt,
order: .reverse
)
private var memos: [Memo]
var body: some View {
List(memos) { memo in
Text(memo.title)
}
}
}이 구조에서는 목록을 표시하기 위해 별도의 배열 복사와 수동 갱신 로직을 작성할 필요가 줄어듭니다.
UPDATE — 메모 수정
12. 모델 속성 변경하기
SwiftData에서는 조회한 모델 객체의 속성을 변경할 수 있습니다.
func updateMemo(
memo: Memo,
title: String,
content: String,
context: ModelContext
) throws {
memo.title = title
memo.content = content
memo.updatedAt = .now
try context.save()
}모델 객체의 변경 사항은 SwiftData가 추적합니다.
따라서 일반적인 수정 작업에서 별도의 SQL UPDATE 문을 작성하지 않아도 됩니다.
DELETE — 메모 삭제
13. 모델 삭제하기
func deleteMemo(
memo: Memo,
context: ModelContext
) throws {
context.delete(memo)
try context.save()
}SwiftData에서는 delete()로 모델 삭제를 요청할 수 있습니다.
삭제 후 save()를 호출하면 변경 사항이 저장됩니다.
CRUD Repository 구현
14. MemoRepository
실제 프로젝트에서는 데이터 접근 로직을 별도 객체로 분리하면 테스트와 유지보수에 유리합니다.
MemoRepository.swift
import Foundation
import SwiftData
@MainActor
final class MemoRepository {
private let context: ModelContext
init(context: ModelContext) {
self.context = context
}
func fetchAll() throws -> [Memo] {
let descriptor = FetchDescriptor<Memo>(
sortBy: [
SortDescriptor(
\Memo.createdAt,
order: .reverse
)
]
)
return try context.fetch(descriptor)
}
func add(
title: String,
content: String
) throws {
let memo = Memo(
title: title,
content: content
)
context.insert(memo)
try context.save()
}
func update(
memo: Memo,
title: String,
content: String
) throws {
memo.title = title
memo.content = content
memo.updatedAt = .now
try context.save()
}
func delete(_ memo: Memo) throws {
context.delete(memo)
try context.save()
}
func toggleFavorite(_ memo: Memo) throws {
memo.isFavorite.toggle()
memo.updatedAt = .now
try context.save()
}
}여기서는 @MainActor를 사용해 Repository가 메인 액터에서 동작하도록 했습니다.
이 예제는 소규모 메모 앱을 위한 구조입니다.
대량 데이터 처리나 백그라운드 작업이 필요하다면 별도의 ModelActor와 ModelContext를 활용하는 설계를 검토해야 합니다.
MVVM 구조 적용
15. SwiftData와 ViewModel
SwiftData를 사용할 때는 두 가지 접근 방식이 있습니다.
첫 번째는 View에서 @Query를 직접 사용하는 방식입니다.
두 번째는 ViewModel과 Repository를 통해 데이터를 관리하는 방식입니다.
둘 다 사용할 수 있지만 목적이 다릅니다.
| 방식 | 장점 | 고려사항 |
|---|---|---|
| View + @Query | 코드가 간단하고 UI 갱신이 편리 | View가 영속성 계층을 직접 인식 |
| ViewModel + Repository | 로직 분리와 테스트에 유리 | 변경 사항을 UI에 반영하는 구조 필요 |
단순한 메모 앱이라면 @Query를 사용하는 방식이 간단합니다.
반면 여러 화면에서 복잡한 비즈니스 로직을 공유한다면 Repository와 ViewModel을 분리하는 것이 유리할 수 있습니다.
실제 메모 앱 화면 구현
16. 메모 목록과 추가 기능
이번에는 @Query를 이용한 SwiftUI 메모 목록 화면을 작성하겠습니다.
MemoListView.swift
import SwiftUI
import SwiftData
struct MemoListView: View {
@Environment(\.modelContext)
private var modelContext
@Query(
sort: \Memo.createdAt,
order: .reverse
)
private var memos: [Memo]
@State private var showingEditor = false
@State private var errorMessage: String?
var body: some View {
NavigationStack {
List {
ForEach(memos) { memo in
NavigationLink {
MemoDetailView(memo: memo)
} label: {
HStack {
VStack(
alignment: .leading,
spacing: 6
) {
Text(memo.title)
.font(.headline)
Text(
memo.createdAt,
format: .dateTime
.year()
.month()
.day()
)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
if memo.isFavorite {
Image(
systemName: "star.fill"
)
.foregroundStyle(.yellow)
}
}
}
}
.onDelete(perform: deleteMemos)
}
.navigationTitle("메모")
.toolbar {
ToolbarItem(
placement: .topBarTrailing
) {
Button {
showingEditor = true
} label: {
Image(systemName: "plus")
}
}
}
.sheet(
isPresented: $showingEditor
) {
MemoEditorView()
}
.alert(
"오류",
isPresented: Binding(
get: { errorMessage != nil },
set: { if !$0 { errorMessage = nil } }
)
) {
Button("확인", role: .cancel) {}
} message: {
Text(errorMessage ?? "")
}
}
}
private func deleteMemos(
at offsets: IndexSet
) {
for index in offsets {
modelContext.delete(memos[index])
}
do {
try modelContext.save()
} catch {
errorMessage = "메모 삭제에 실패했습니다."
}
}
}@Query가 데이터를 조회하고, 목록의 변경 사항을 UI에 반영합니다.
메모 작성 화면
17. MemoEditorView
import SwiftUI
import SwiftData
struct MemoEditorView: View {
@Environment(\.modelContext)
private var modelContext
@Environment(\.dismiss)
private var dismiss
@State private var title = ""
@State private var content = ""
@State private var errorMessage: String?
var body: some View {
NavigationStack {
Form {
Section("제목") {
TextField(
"메모 제목",
text: $title
)
}
Section("내용") {
TextEditor(text: $content)
.frame(minHeight: 180)
}
}
.navigationTitle("새 메모")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(
placement: .cancellationAction
) {
Button("취소") {
dismiss()
}
}
ToolbarItem(
placement: .confirmationAction
) {
Button("저장") {
saveMemo()
}
.disabled(
title.trimmingCharacters(
in: .whitespacesAndNewlines
).isEmpty
)
}
}
.alert(
"저장 오류",
isPresented: Binding(
get: { errorMessage != nil },
set: { if !$0 { errorMessage = nil } }
)
) {
Button("확인", role: .cancel) {}
} message: {
Text(errorMessage ?? "")
}
}
}
private func saveMemo() {
let memo = Memo(
title: title.trimmingCharacters(
in: .whitespacesAndNewlines
),
content: content
)
modelContext.insert(memo)
do {
try modelContext.save()
dismiss()
} catch {
modelContext.delete(memo)
errorMessage = "메모를 저장하지 못했습니다."
}
}
}저장에 성공하면 작성 화면이 닫히고, 목록에는 새 메모가 반영됩니다.
실제 서비스에서는 저장 실패 후 컨텍스트의 변경 상태를 복구하는 정책을 별도로 설계하는 것이 좋습니다.
메모 상세 화면
18. MemoDetailView
import SwiftUI
import SwiftData
struct MemoDetailView: View {
@Environment(\.modelContext)
private var modelContext
@Bindable var memo: Memo
@State private var errorMessage: String?
var body: some View {
Form {
Section("제목") {
TextField(
"제목",
text: $memo.title
)
}
Section("내용") {
TextEditor(text: $memo.content)
.frame(minHeight: 200)
}
Section {
Toggle(
"즐겨찾기",
isOn: $memo.isFavorite
)
}
}
.navigationTitle("메모 상세")
.toolbar {
ToolbarItem(
placement: .topBarTrailing
) {
Button("저장") {
memo.updatedAt = .now
do {
try modelContext.save()
} catch {
errorMessage = "수정 내용을 저장하지 못했습니다."
}
}
}
}
.alert(
"오류",
isPresented: Binding(
get: { errorMessage != nil },
set: { if !$0 { errorMessage = nil } }
)
) {
Button("확인", role: .cancel) {}
} message: {
Text(errorMessage ?? "")
}
}
}이 예제에서는 @Bindable을 이용해 SwiftData 모델의 속성을 입력 UI와 연결했습니다.
다만 이 방식은 사용자가 입력하는 동안 모델 속성이 직접 변경됩니다.
따라서 취소 버튼을 눌렀을 때 변경 내용을 버려야 하는 앱이라면 별도의 편집용 상태를 사용하는 것이 좋습니다.
SwiftData 검색 기능
19. Predicate를 이용한 검색
SwiftData에서는 #Predicate를 이용해 조회 조건을 정의할 수 있습니다.
예를 들어 제목에 특정 문자열이 포함된 메모를 검색할 수 있습니다.
func searchMemos(
keyword: String,
context: ModelContext
) throws -> [Memo] {
let descriptor = FetchDescriptor<Memo>(
predicate: #Predicate<Memo> { memo in
memo.title.localizedStandardContains(keyword)
},
sortBy: [
SortDescriptor(
\Memo.createdAt,
order: .reverse
)
]
)
return try context.fetch(descriptor)
}#Predicate는 SwiftData가 처리할 수 있는 조건을 정의하는 데 사용합니다.
모든 Swift 문자열 메서드가 영속성 Predicate에서 지원되는 것은 아니므로, 실제 사용하는 SDK에서 지원 여부를 확인해야 합니다.
즐겨찾기 검색
20. 즐겨찾기 메모만 조회하기
@Query(
filter: #Predicate<Memo> {
$0.isFavorite == true
},
sort: \Memo.createdAt,
order: .reverse
)
private var favoriteMemos: [Memo]이렇게 하면 즐겨찾기로 설정한 메모만 목록에 표시할 수 있습니다.
Android Room에서 WHERE is_favorite = 1 조건을 사용하는 것과 목적이 비슷합니다.
데이터 정렬
21. SortDescriptor
SwiftData에서는 SortDescriptor를 이용해 정렬할 수 있습니다.
최신순 정렬:
SortDescriptor(
\Memo.createdAt,
order: .reverse
)제목순 정렬:
SortDescriptor(
\Memo.title,
order: .forward
)여러 정렬 조건을 조합할 수도 있습니다.
UserDefaults에서 SwiftData로 이전
22. 기존 데이터 마이그레이션이 필요한 이유
지난 글에서는 메모 데이터를 UserDefaults에 JSON으로 저장했습니다.
이번 글에서는 SwiftData를 사용합니다.
따라서 기존 사용자가 앱을 업데이트했을 때 저장된 메모를 유지하려면 데이터 이전 작업이 필요합니다.
예를 들어 다음과 같은 흐름입니다.
UserDefaults
│
▼
기존 JSON 읽기
│
▼
LegacyMemo 디코딩
│
▼
SwiftData Memo 생성
│
▼
ModelContext 저장
│
▼
이전 완료 표시여기서 중요한 점은 기존 데이터를 바로 삭제하지 않는 것입니다.
새 저장소에 데이터가 정상적으로 저장되었는지 확인한 뒤 이전 완료 상태를 기록해야 합니다.
23. 기존 데이터 모델 정의
이전 글의 JSON 구조를 읽기 위한 모델을 별도로 정의합니다.
import Foundation
struct LegacyMemo: Decodable {
let id: UUID
let title: String
let content: String?
let isFavorite: Bool?
let createdAt: Date
}기존 데이터에 없던 필드는 Optional로 처리합니다.
24. 데이터 이전 코드
다음은 기본적인 데이터 이전 예제입니다.
import Foundation
import SwiftData
@MainActor
func migrateLegacyMemos(
context: ModelContext
) throws {
let defaults = UserDefaults.standard
let migrationKey = "swiftdata_migration_v1"
if defaults.bool(forKey: migrationKey) {
return
}
guard let data = defaults.data(
forKey: "saved_memos"
) else {
defaults.set(
true,
forKey: migrationKey
)
return
}
let oldMemos = try JSONDecoder().decode(
[LegacyMemo].self,
from: data
)
let existingMemos = try context.fetch(
FetchDescriptor<Memo>()
)
let existingIds = Set(
existingMemos.map(\.id)
)
for oldMemo in oldMemos {
if existingIds.contains(oldMemo.id) {
continue
}
let memo = Memo(
id: oldMemo.id,
title: oldMemo.title,
content: oldMemo.content ?? "",
isFavorite: oldMemo.isFavorite ?? false,
createdAt: oldMemo.createdAt
)
context.insert(memo)
}
try context.save()
defaults.set(
true,
forKey: migrationKey
)
}이 예제에서는 UUID를 이용해 기존 메모와 중복되는 데이터를 확인합니다.
다만 대량 데이터나 부분 실패가 발생할 수 있는 실제 서비스에서는 배치 처리, 충돌 정책, 검증 및 복구 절차를 추가해야 합니다.
SwiftData 스키마 마이그레이션
25. 모델 속성이 변경되면?
앱 업데이트 과정에서 데이터 모델이 변경될 수 있습니다.
예를 들어 다음과 같은 변화입니다.
Version 1
├── title
└── content
Version 2
├── title
├── content
└── category이러한 변경을 관리하기 위해 SwiftData에서는 VersionedSchema와 SchemaMigrationPlan을 사용할 수 있습니다.
단순한 변경은 경량 마이그레이션으로 처리할 수 있지만, 모든 변경이 자동으로 처리되는 것은 아닙니다.
특히 속성 삭제, 타입 변경, 관계 구조 변경, 고유성 제약 변경 등은 별도 검토가 필요합니다.
26. VersionedSchema 개념
SwiftData에서는 버전별 스키마를 정의할 수 있습니다.
enum MemoSchemaV1: VersionedSchema {
static var versionIdentifier:
Schema.Version {
Schema.Version(1, 0, 0)
}
static var models: [any PersistentModel.Type] {
[Memo.self]
}
}위 코드는 구조를 설명하기 위한 간단한 예제입니다.
실제 버전 관리에서는 각 버전의 모델을 독립적으로 정의하고, 해당 버전의 모델을 스키마에 등록해야 합니다.
현재 사용하는 모델 하나를 여러 버전에서 그대로 공유하는 방식은 적절하지 않습니다.
SwiftData 성능 최적화
27. 모든 데이터를 한 번에 가져오지 않기
메모가 수천 개 이상으로 증가하면 전체 데이터를 한 번에 가져오는 방식이 비효율적일 수 있습니다.
이 경우 FetchDescriptor의 조회 범위를 제한하는 방법을 검토할 수 있습니다.
var descriptor = FetchDescriptor<Memo>(
sortBy: [
SortDescriptor(
\Memo.createdAt,
order: .reverse
)
]
)
descriptor.fetchLimit = 50이 코드는 최대 50개 항목을 조회하도록 설정합니다.
실제 페이지네이션을 구현하려면 다음 페이지의 기준과 데이터 변경 시 중복·누락 처리까지 설계해야 합니다.
28. 백그라운드 작업
SwiftData의 ModelContext는 실행 컨텍스트와 액터 격리를 고려해 사용해야 합니다.
메인 화면에서 사용하는 컨텍스트를 임의의 백그라운드 스레드로 전달하는 방식은 피해야 합니다.
대량 데이터 가져오기나 동기화 작업에서는 ModelActor 등을 이용해 별도의 컨텍스트를 관리하는 방법을 검토할 수 있습니다.
데이터 저장 방식 선택
29. UserDefaults, SwiftData, SQLite 비교
| 저장 방식 | 적합한 데이터 |
|---|---|
| UserDefaults | 앱 설정, 작은 상태값 |
| SwiftData | 메모, 일정, 즐겨찾기, 관계형 모델 |
| SQLite 직접 사용 | SQL 제어가 필요한 데이터 |
| 파일 저장 | 이미지, 문서, GPX 등 |
모든 데이터를 SwiftData에 저장할 필요는 없습니다.
예를 들어 사진 파일은 파일 시스템에 저장하고, 해당 사진의 경로와 메타데이터만 데이터베이스에 저장할 수 있습니다.
테스트 체크리스트
30. SwiftData 적용 전 확인 사항
iOS Deployment Target 확인
@Model 모델 정의
ModelContainer 설정
ModelContext 주입
메모 추가
메모 조회
메모 수정
메모 삭제
@Query 목록 갱신
검색 기능
즐겨찾기 필터
날짜 정렬
앱 재실행 후 데이터 유지
UserDefaults 데이터 이전
기존 메모 중복 방지
마이그레이션 실패 처리
스키마 버전 변경 테스트
대량 데이터 조회 성능
실제 iPhone 테스트
31. 마무리
SwiftData를 활용하면 SwiftUI 앱에서 데이터베이스 기능을 비교적 간결하게 구현할 수 있습니다.
특히 @Model, ModelContext, @Query를 이해하면 기본적인 CRUD 기능을 구현하는 데 필요한 코드가 줄어듭니다.
이번 글에서 살펴본 핵심 내용을 정리하면 다음과 같습니다.
SwiftData는 iOS 17 이상에서 사용할 수 있습니다.
@Model을 이용해 영속성 모델을 정의합니다.
ModelContainer는 데이터 저장소 구성을 관리합니다.
ModelContext를 이용해 CRUD 작업을 수행합니다.
@Query로 SwiftUI 목록과 데이터를 연결할 수 있습니다.
FetchDescriptor와 Predicate를 이용해 검색과 정렬을 구현합니다.
Repository를 이용해 데이터 접근 로직을 분리할 수 있습니다.
기존 UserDefaults 데이터는 별도 이전 절차가 필요합니다.
모델 변경 시 스키마 마이그레이션을 검토해야 합니다.
대량 데이터에서는 조회 범위와 액터 격리를 고려해야 합니다.
Android에서 SQLite나 Room을 사용해 본 개발자라면 SwiftData의 개념을 이해하는 데 도움이 될 것입니다.
다만 SwiftData는 Room과 동일한 방식으로 동작하지 않으므로 Apple 플랫폼의 모델 생명주기와 상태 추적 방식을 이해하는 것이 중요합니다.
SwiftData를 사용하는 목적은 단순히 SQL 코드를 줄이는 것이 아니라, 데이터 모델과 SwiftUI 화면을 안정적으로 연결하는 데 있습니다.
참고 자료
SwiftData 공식 문서
https://developer.apple.com/documentation/swiftdataModel
https://developer.apple.com/documentation/swiftdata/modelModelContainer
https://developer.apple.com/documentation/swiftdata/modelcontainerModelContext
https://developer.apple.com/documentation/swiftdata/modelcontextQuery
https://developer.apple.com/documentation/swiftdata/queryFetchDescriptor
https://developer.apple.com/documentation/swiftdata/fetchdescriptorSwiftData 마이그레이션
https://developer.apple.com/documentation/swiftdata/schemamigrationplanObservation
https://developer.apple.com/documentation/observation
태그: iOS, Swift, SwiftUI, SwiftData, Xcode, 데이터베이스, CRUD, ModelContainer, ModelContext, Query, FetchDescriptor, Predicate, MVVM, Repository, UserDefaults 마이그레이션, iPhone 앱 개발