안녕하세요! 피피아노입니다 🎵
SwiftData를 사용해서 앱을 개발하다 보면 한 번쯤 이런 경험을 하게 됩니다.
모델 구조를 살짝 바꿨을 뿐인데 앱이 실행되자마자 바로 런타임 에러가 발생하는 경우 말이죠.
물론 개발 중이라면 앱을 삭제하고 다시 설치하면 문제없이 돌아가지만, 출시된 앱이라면 “앱을 지우고 다시 설치하세요”라고 안내할 수도 없습니다. 그리고 무엇보다 매번 삭제하면서 앱을 빌드하고 테스트하기가 귀찮습니다...
그렇다면 이 문제는 왜 발생하고, 어떻게 해결해야 할까요?
원인 - SwiftData의 내부 구조
SwiftData는 내부적으로 SQLite 기반의 데이터베이스를 사용합니다.
@Model로 정의한 구조체는 단순한 Swift 타입이 아니라, SwiftData가 해당 구조를 읽어서 데이터베이스 스키마를 자동으로 생성해줍니다.
즉, 모델 구조가 "데이터베이스 설계도" 역할을 하고 있는 것입니다.
그래서 아래와 같은 변경이 발생하게 되면 기존 DB(데이터베이스)의 구조와 불일치하게 되어 충돌이 발생해서 앱이 크래시가 발생합니다.
변경 내용 | 결과 |
프로퍼티 추가 | 새로운 컬럼이 DB에 없음 |
프로퍼티 타입 변경 | 기존 데이터와 호환 불가 |
프로퍼티 삭제 | DB 컬럼이 존재하지만 모델에 없음 |
관계(@Relationship) 변경 | 참조 무결성 깨짐 |
이런 경우 SwiftData는 DB를 열 수 없다고 판단하고 앱을 중단시킵니다.
개발 중이라면 앱 삭제로 DB를 초기화하면 되지만, 사용자 데이터가 있는 실제 서비스 환경에서는 치명적인 문제입니다.
개발 중에 임시로 해결하는 방법
개발 단계에서는 DB를 깨끗하게 비워버리는 방법이 사실 가장 간단하긴 합니다.
- 앱을 삭제하고 다시 설치
- 시뮬레이터에서 "Erase All Content and Setting" 실행
- ModelContainer를 임시로 메모리 전용(isStoredInMemoryOnly: true)으로 설정
let configuration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: true
)
하지만 이 방법은 어디까지나 개발용 임시 해결책입니다.
해결책: 자동 마이그레이션(migrateAutomatically)
해당 문제를 해결하려면 자동 마이그레이션(migrateAutomatically) 기능을 사용하면 됩니다.
이 옵션을 설정하면 SwiftData가 모델 변경을 감지하고 가능한 범위 내에서 자동으로 스키마를 업데이트해줍니다.
import SwiftUI
import SwiftData
@main
struct TestApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Project.self,
])
let modelConfiguration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false,
allowsSave: true,
migrateAutomatically: true // 자동 마이그레이션 활성화
)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
자동 마이그레이션이 처리할 수 있는 변경 범위
변경 유형 | 자동 마이그레이션 가능 여부 |
새로운 프로퍼티 추가(기본값 있음) | 가능 |
옵셔널 -> non-optional(기본값 존재 시) | 가능 |
프로퍼티 삭제 | 불가능 |
프로퍼티 타입 변경(String -> Int) | 불가능 |
관계(@Relationship) 구조 변경 | 불가능 |
이름 변경 | @Attribute(.rename(from:)) 사용 시 가능 |
즉, 자동 마이그레이션은 가벼운 변경은 처리할 수 있지만, 타입 변경이나 관계 변경이 필요한 경우에는 직접 마이크레이션 코드를 작성해야 합니다.
수동 마이그레이션
수동 마이그레이션은 기존 데이터를 임시로 읽은 뒤, 새로운 모델 구조로 변환하여 다시 저장하는 방식입니다.
예를 들어, Project 모델의 프로퍼티 이름이나 타입이 바뀌었다면 앱 실행 시점에서 이전 데이터를 읽어서 새 모델 인스턴스로 매핑할 수 있습니다.
if let oldProjects = try? context.fetch(FetchDescriptor<OldProject>()) {
for old in oldProjects {
let new = Project(title: old.title, createdAt: old.date)
context.insert(new)
}
try? context.save()
}
마무리
SwiftData는 편리하지만 내부적으로는 SQLite 기반의 엄연한 데이터베이스 시스템이기 때문에 모델 구조를 바꾸는 건 곧 DB 스키마를 바꾸는 것이고 그에 맞는 마이그레이션 과정을 반드시 고려해야 합니다.
저도 처음에는 앱이 크래시 날 때마다 앱을 지웠다가 다시 설치하면 된다고 생각했지만, 현업에서는 절대 이런 방법을 쓰지 않겠구나 라는 생각이 문득 들어서 이번 경험을 통해 정리를 해보았습니다. 다른 분들도 도움이 되셨으면 좋겠습니다!
감사합니다!
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > Swift' 카테고리의 다른 글
[Swift] 앱 인텐트 알아보기 (4) | 2025.09.03 |
---|---|
[Swift] Multipeer Connectivity 톺아보기 (5) | 2025.08.23 |
[Swift] Swift Testing 톺아보기 (6) | 2025.07.02 |
[Swift] suffix()로 인한 시간 초과 문제 해결하기 (8) | 2025.06.27 |
[Swift] Foundation Models Framework (6) | 2025.06.22 |