반응형

SwiftUI 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와 유지보수성이 크게 향상된다.
반응형
Posted by 까칠코더
,