SwiftUI의 @AppStorage는 UserDefaults와 함께 작동하는 property wrapper로, 값을 UI와 자동으로 동기화합니다. @AppStorage로 선언된 프로퍼티가 변경되면 새 값이 즉시 UserDefaults에 저장되고, UserDefaults가 외부(위젯 또는 확장 기능)에서 변경되면 View가 자동으로 다시 그려집니다. Apple Developer Documentation (2025)에 따르면 @AppStorage는 String, Int, Double, Bool, Data, URL 및 선택적 버전을 지원하여 수동 관찰 코드 없이 사용자 설정의 반응형 저장을 제공합니다.
핵심 사항
@AppStorage는 Apple이 iOS 14에서 도입한 property wrapper로, View 프로퍼티를 UserDefaults의 키에 바인딩합니다. 프로퍼티를 읽을 때 SwiftUI는 지정된 키로 UserDefaults에서 값을 로드합니다. 쓸 때는 새 값을 저장하고 View에 다시 그려야 함을 알립니다.
@AppStorage 이전에는 개발자가 onAppear에서 수동으로 UserDefaults를 읽고, UserDefaults.didChangeNotification을 구독하고, 변경 시 @State를 업데이트해야 했습니다. @AppStorage는 전체 사이클을 자동화합니다. 한 줄의 선언으로 15-20줄의 보일러플레이트 코드를 대체합니다. 또한 @AppStorage는 양방향 동기화를 제공합니다. 다른 프로세스(App Extension 또는 Widget)에서 UserDefaults 값이 변경되어도 View는 계속 업데이트를 받습니다.
아키텍처적으로 @AppStorage는 DynamicProperty로 구현되어 SwiftUI가 종속성을 추적하고 관찰된 값이 변경될 때 View를 다시 그릴 수 있게 합니다. 이는 사용자 설정(인터페이스 언어, 기능 활성화/비활성화, 마지막 선택 탭, 사용자 이름)을 저장하는 데 이상적입니다.
@AppStorage는 내부적으로 UserDefaults를 사용하지만 저장소 작업 방식은 근본적으로 다릅니다. UserDefaults는 읽기, 쓰기 및 변경 알림의 수동 관리가 필요한 저수준 API입니다. @AppStorage는 즉시 사용 가능한 반응형 동작을 제공하는 SwiftUI 추상화입니다.
UserDefaults는 일회성 작업(앱 시작 시 설정 로드, 분석 데이터 쓰기, 토큰 캐싱)에 적합합니다. @AppStorage는 UI를 반응형으로 업데이트해야 하는 설정(테마 전환, 언어 선택, 인터페이스 상태 저장)을 위한 것입니다. View 내에서 UserDefaults를 직접 사용하는 것은 안티패턴입니다. 추가 구독 없이는 View가 변경 사항을 알 수 없기 때문입니다.
| 매개변수 | @AppStorage | UserDefaults |
|---|---|---|
| 반응성 | 자동 | 알림 구독 필요 |
| 보일러플레이트 | 프로퍼티당 1줄 | 프로퍼티당 15-20줄 |
| 유형 | String, Int, Double, Bool, Data, URL | 모든 유형 + 아카이브 객체 |
| 사용자 정의 유형 | RawRepresentable 통해 | NSKeyedArchiver 통해 |
| App Extension | 자동 동기화 | 수동 구독 |
반응형 UI가 있는 간단한 설정의 경우 @AppStorage가 선호되는 선택입니다. 복잡한 데이터(배열, 딕셔너리, 사용자 정의 객체)의 경우 UserDefaults를 @State 및 수동 변경 구독과 조합하여 사용하거나 구조화된 저장을 위해 SwiftData / Core Data로 전환하세요.
@AppStorage는 UserDefaults가 직접 직렬화할 수 있는 표준 유형을 지원합니다: String, Int, Double, Bool, Data, URL. 각 유형마다 선택적 버전(String?, Int?, Double?, Bool?, Data?, URL?)이 있어 "설정되지 않음"과 "빈 값"을 구분할 수 있습니다.
RawRepresentable 프로토콜을 준수하는 사용자 정의 유형을 저장하는 경우 @AppStorage도 자동으로 작동합니다. enum의 rawValue가 String 또는 Int 유형이면 직접 사용할 수 있습니다: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI는 rawValue를 통해 값을 자동으로 직렬화/역직렬화합니다.
enum AppTheme: String {
case system, light, dark
}
struct SettingsView: View {
@AppStorage("username") var username: String = "Guest"
@AppStorage("launchCount") var launchCount: Int = 0
@AppStorage("isDarkMode") var isDarkMode: Bool = false
@AppStorage("appTheme") var theme: AppTheme = .system
@AppStorage("lastOpened") var lastOpened: Date? = nil
var body: some View {
Form {
TextField("Username", text: $username)
Toggle("Dark mode", isOn: $isDarkMode)
Text("\(launchCount)회 실행됨")
}
}
}
예제에서는 다양한 @AppStorage 유형을 사용합니다: 기본값 "Guest"의 String, 실행 카운터의 Int, 다크 테마의 Bool, String 유형의 rawValue를 가진 AppTheme enum, 마지막 열기 시간의 선택적 Date?. 각 프로퍼티는 첫 번째 인수로 지정된 UserDefaults 키에 바인딩됩니다. 첫 실행 시 키가 저장소에 없으면 기본값이 사용됩니다.
@AppStorage의 주요 장점 중 하나는 모든 소스에서 UserDefaults 변경을 자동으로 관찰한다는 것입니다. App Extension 또는 Widget이 값을 변경하면 부모 앱의 @AppStorage가 알림을 받고 View를 다시 그립니다. 이는 @AppStorage가 UserDefaults.didChangeNotification에 자동으로 설정하는 KVO(Key-Value Observing) 메커니즘을 통해 달성됩니다.
실제로 이는 사용자가 Widget에서 설정을 변경하면(예: 다크 테마 활성화) 앱이 즉시 변경 사항을 감지함을 의미합니다. 메인 앱과 Share Extension, Watch App, Today Widget 간에도 동일한 동기화가 작동합니다. 개발자는 프로세스 간 데이터 교환 코드를 작성할 필요가 없습니다 — @AppStorage가 자동으로 처리합니다.
struct ThemeSettingView: View {
@AppStorage("isDarkMode") var isDarkMode: Bool = false
var body: some View {
VStack {
Toggle("Dark Mode", isOn: $isDarkMode)
.onChange(of: isDarkMode) { oldValue, newValue in
print("다크 모드가 \(newValue)(으)로 변경됨")
}
}
}
}
Toggle은 @AppStorage를 통해 $isDarkMode에 바인딩됩니다. 전환 시 값이 "isDarkMode" 키로 UserDefaults에 자동 저장됩니다. .onChange 수정자를 사용하면 변경 시 부수 효과를 수행할 수 있습니다(예: 분석 데이터 보내기 또는 다른 화면의 UI 업데이트). Widget이 동일한 키를 변경하면 @AppStorage도 onChange를 트리거하여 상태 일관성을 보장합니다.
모든 구성을 저장하기 위해 @AppStorage를 사용하는 완전한 앱 설정 화면을 살펴보겠습니다. 양식에는 다양한 유형의 설정 섹션(텍스트 필드, 토글, 카운터)이 포함되어 있으며 모든 값이 자동으로 UserDefaults에 저장됩니다.
struct AppSettingsView: View {
@AppStorage("displayName") var displayName = ""
@AppStorage("notificationsEnabled") var notificationsEnabled = true
@AppStorage("maxResults") var maxResults = 25
@AppStorage("selectedTab") var selectedTab = "home"
var body: some View {
NavigationStack {
Form {
Section(header: Text("프로필")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("환경설정")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("설정 재설정") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
양식에는 다양한 유형의 네 가지 @AppStorage 프로퍼티가 포함됩니다: 이름용 String, 알림용 Bool, 결과 수용 Int, 선택된 탭용 String. 모든 컨트롤은 Binding($displayName, $notificationsEnabled 등)을 통해 프로퍼티에 바인딩됩니다. "Reset settings" 버튼은 앱 도메인을 제거하여 모든 UserDefaults를 지우며, 이후 @AppStorage는 자동으로 기본값으로 돌아갑니다.
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "시스템"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "파란색"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("시스템").tag("system")
Text("라이트").tag("라이트")
Text("다크").tag("다크")
}
Picker("Accent color", selection: $widgetColor) {
Text("파란색").tag("blue")
Text("초록색").tag("초록색")
Text("빨간색").tag("빨간색")
}
}
}
}
App Group(앱과 확장 기능 간 공유 저장소)의 경우 @AppStorage는 store 매개변수를 허용합니다: UserDefaults(suiteName:). 값은 메인 앱, Widget, Watch App 및 동일한 그룹의 다른 확장 기능에서 액세스할 수 있는 공유 컨테이너에 저장됩니다. Widget은 이러한 설정을 읽을 수 있으며, 앱에서 설정이 변경되면 UserDefaults 관찰 메커니즘을 통해 Widget이 자동으로 업데이트됩니다.
자주 묻는 질문
@State는 값만 메모리에 저장하고 앱을 다시 시작하면 재설정됩니다. @AppStorage는 값을 UserDefaults에 저장하고 다음 실행 시 복원합니다. 임시 화면 데이터에는 @State를, 재시작 후에도 유지되어야 하는 설정에는 @AppStorage를 사용하세요.
네, Enum이 String 또는 Int 유형의 rawValue로 RawRepresentable 프로토콜을 구현하는 경우 가능합니다. 예: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI는 rawValue를 통해 enum을 자동으로 직렬화하고 로드 시 복원합니다.
표준 저장소의 경우 UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)를 호출하거나 특정 키의 경우 removeObject(forKey:)를 호출하세요. 지운 후 모든 @AppStorage 프로퍼티는 선언 시 지정된 기본값으로 돌아갑니다.
네, 앱과 확장 기능 간 동기화를 위해 App Group을 사용하세요: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!). Widget, Share Extension 및 Watch App이 동일한 UserDefaults를 읽고 쓸 수 있으며 변경 사항이 자동으로 추적됩니다.
@AppStorage는 UserDefaults를 사용하며, 이는 소량의 데이터(설정, 토큰, 카운터)용으로 설계되었습니다. 권장 한도는 앱당 최대 100KB입니다. 구조화된 데이터나 대용량 데이터(객체 배열, 미디어 파일)의 경우 SwiftData, Core Data 또는 파일 시스템을 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.