SwiftUI Study – AppStorage·SceneStorage를 남용하지 않고 ‘영속 저장 vs 일시 상태’ 경계를 올바르게 나누는 설계 패턴
Dev Study/SwiftUI 2025. 12. 11. 11:25SwiftUI Study – AppStorage·SceneStorage를 남용하지 않고 ‘영속 저장 vs 일시 상태’ 경계를 올바르게 나누는 설계 패턴
1. 왜 중요한가 (문제 배경)
SwiftUI는 @AppStorage 와 @SceneStorage 를 제공해 View에서 간단하게
상태를 저장할 수 있도록 돕는다. 그러나 실무에서는 다음과 같은 문제가 매우 자주 발생한다.
- AppStorage를 UI 상태(토글, 임시 선택 값 등)에 사용하여 불필요하게 디스크에 저장됨
- SceneStorage를 여러 화면에서 남용하여 의도치 않은 상태 유지가 발생
- “앱 재실행 시 유지해야 하는 값”과 “현재 화면에서만 필요한 값”의 경계가 모호함
- 임시 값이 영구 저장되거나, 영구 데이터가 세션 종료와 함께 사라지는 문제 발생
즉, 상태의 수명(lifecycle) 을 구분하지 않고 Storage 계열을 사용하면
데이터 무결성, UI 품질, 유지보수성에 모두 악영향을 준다.
2. 잘못된 패턴 예시
❌ 예시 1: 임시 UI 상태를 AppStorage에 저장
@AppStorage("selectedTab") private var selectedTab = 0 // ❌ 단순 UI 상태인데 영구 저장됨
TabView(selection: $selectedTab) {
HomeView().tag(0)
ProfileView().tag(1)
}
문제점
- 사용자가 앱을 다시 켜면 항상 이전 탭으로 고정되는 예상치 못한 UX
- 이 값은 영구 저장할 필요가 없는 ‘일시 UI 상태’
❌ 예시 2: SceneStorage를 남용해 화면 전환 간 불필요한 상태 유지
@SceneStorage("form_step") private var step: Int = 0 // ❌ View 전환 후에도 step 유지됨
문제점
- 멀티 스텝 폼을 처음부터 시작해야 하는 상황에서도,
이전 세션의 step 값이 남아 잘못된 페이지로 진입
- SceneStorage는 “장면(scene) 단위 유지”이므로 지나치게 오래 살아남는다
❌ 예시 3: 영구 저장해야 할 데이터를 SceneStorage에 넣은 경우
@SceneStorage("draft_memo") private var draft = "" // ❌ Scene 종료 시 유실
문제점
- SceneStorage는 앱 종료 시 보존되지 않음
- 텍스트 초안이 사라지는 심각한 데이터 손실 가능성
❌ 예시 4: AppStorage에 복잡 객체를 억지로 저장
@AppStorage("user_profile") private var profileData = Data() // ❌ 구조체 인코딩/디코딩 반복
문제점
- JSON 인코딩 디코딩 반복 생성
- UserDefaults 용량 제한 초과 위험
- 앱 성능 및 데이터 무결성 문제 발생 가능
3. 올바른 패턴 예시
✅ 예시 1: UI 상태는 State / ViewModel에서 관리하고 저장하지 않는다
struct ContentView: View {
@State private var selectedTab = 0 // ✔ 일시 상태에 적절
var body: some View {
TabView(selection: $selectedTab) { ... }
}
}
장점
- 사용자가 앱을 재실행할 때 초기 상태로 돌아가는 자연스러운 UX
- 디스크 저장 오염 없음
✅ 예시 2: AppStorage는 “사용자 설정·환경 설정” 같은 영구 상태에만 사용
@AppStorage("user_theme") private var theme: String = "light"
Toggle("다크 모드", isOn: Binding(
get: { theme == "dark" },
set: { theme = $0 ? "dark" : "light" }
))
적합한 AppStorage 사용 예
- 테마
- 언어 설정
- 알림 온/오프
- 사용자 정의 옵션 등
✅ 예시 3: SceneStorage는 “해당 화면 복귀 시 복원해야 하는 값”에 사용
struct MemoEditor: View {
@SceneStorage("memo_text") private var text = "" // ✔ 화면 전환 후에도 복원됨
var body: some View { TextEditor(text: $text) }
}
적합한 SceneStorage 사용 예
- 작성 중인 텍스트
- 스크롤 위치
- 사용자가 이전 화면으로 돌아올 가능성이 높은 값
❗ AppStorage vs SceneStorage — 선택 기준
| 항목 | AppStorage | SceneStorage |
| 저장 위치 | UserDefaults(영구) | 임시 Scene 상태 |
| 앱 종료 후 유지 | ✔ 유지됨 | ❌ 유지되지 않음 |
| 적합한 용도 | 사용자 설정 | 작성 중 UI 상태 |
| 페이지 이동 후 상태 | 유지되지 않음 | 대부분 유지됨 |
| 잘못 사용하면? | 불필요한 영구 저장 | 의도치 않은 상태 복원 |
✅ 예시 4: 영구 저장이 필요한 초안은 AppStorage 또는 로컬 DB 이용
@AppStorage("memo_draft") private var memoDraft = ""
또는
- SwiftData
- Core Data
- 파일 저장 시스템
등 명확한 영속성 도구로 대체해야 함.
✅ 예시 5: Storage를 ViewModel로 감싸서 도메인 계층을 보호
@MainActor
final class SettingsViewModel: ObservableObject {
@AppStorage("user_theme") var theme = "light"
}
View에서는 단순 사용:
@StateObject var vm = SettingsViewModel()
Toggle("다크 모드", isOn: Binding(
get: { vm.theme == "dark" },
set: { vm.theme = $0 ? "dark" : "light" }
))
장점
- View에서 Storage 키 문자열이 사라짐
- 도메인·비즈니스 규칙을 ViewModel에서 통제할 수 있음
4. 실전 적용 팁
✔ 팁 1 — “앱 재실행 후에도 유지돼야 하나?”를 먼저 자문하기
YES → AppStorage
NO → SceneStorage or State / ViewModel
✔ 팁 2 — 단순 UI 상태는 무조건 State 또는 ObservableObject
Storage에 넣지 말 것.
✔ 팁 3 — SceneStorage는 화면 복원 전용
네비게이션 흐름이 단순한 앱에서는 거의 필요 없음.
✔ 팁 4 — 복잡 객체·대용량 데이터는 절대 AppStorage에 저장하지 말 것
UserDefaults는 설정 저장용이지 데이터베이스가 아님.
✔ 팁 5 — Storage 키 문자열은 상수/enum으로 관리
프로젝트 규모가 크면 실수 확률 급증.
5. 정리
- AppStorage와 SceneStorage는 매우 강력하지만, 사용 목적이 완전히 다르다.
- 가장 중요한 기준은 “이 상태가 앱 재실행 후에도 유지되어야 하는가?”이다.
- UI 일시 상태는 State/ObservableObject로, 영구 상태는 AppStorage 또는 데이터베이스로 분리해야 한다.
- Storage 남용을 방지하면 SwiftUI 화면 상태가 예측 가능해지고,
UX와 유지보수성이 크게 향상된다.

