SwiftUI 데이터 흐름 & 단방향 아키텍처
발행: (2025년 12월 13일 오전 08:18 GMT+9)
5 min read
원문: Dev.to
Source: Dev.to
단방향 데이터 흐름이 의미하는 바
- User Action → Event 가 계층 아래로 흐릅니다.
- State 가 계층 위로 올라옵니다.
- 지름길도 없고, 역채널도 없으며, 예상치 못한 변형도 없습니다.
데이터 흐름을 깨는 경우
- 뷰가 전역 상태를 직접 변형
- 서비스가 UI 상태를 스스로 업데이트
- 여러 ViewModel이 동일한 데이터를 편집
- 비즈니스 로직을 직접 뷰에 바인딩
- 기능 경계를 넘어 바인딩을 전달
같은 상태를 두 개 이상의 객체가 변경할 수 있다면 버그는 보장됩니다.
4계층 아키텍처
| Layer | Responsibility |
|---|---|
| Views | 상태를 표시하고, 사용자 인텐트를 전송 (비즈니스 로직 없음) |
| ViewModel | 상태를 소유하고, 인텐트를 처리하며, 비동기 작업을 조정 |
| Services | 부수 효과 수행 (네트워킹, 영속성, 분석) |
| Models | 순수 데이터, 로직 없음, 부수 효과 없음 |
각 계층은 하나의 책임만 가집니다.
뷰: 인텐트를 전송하고, 상태를 변형하지 않음
Button("Like") {
viewModel.likeTapped()
}
이렇게 하면 안 됩니다:
viewModel.post.likes += 1 // ❌
인텐트 기반 API는 로직을 중앙 집중화하고 테스트 가능하게 합니다.
예시 ViewModel
@Observable
class FeedViewModel {
var posts: [Post] = []
var loading = false
func load() async { … }
func likeTapped(postID: String) { … }
}
상태 관리 규칙
- Views는 상태를 읽습니다.
- ViewModels는 상태를 변형합니다.
- Services는 UI 상태를 절대 건드리지 않습니다.
비동기 작업은 뷰모델에 있어야 함
@MainActor
func load() async {
loading = true
defer { loading = false }
do {
posts = try await api.fetchFeed()
} catch {
errors.present(map(error))
}
}
핵심 규칙
- 항상 메인 액터에서 상태를 업데이트합니다.
- 서비스가 뷰모델 속성을 변형하도록 하지 않습니다.
- 비동기 오류는 뷰모델을 통해 다시 흐릅니다.
서비스 설계
좋은 서비스 프로토콜
protocol FeedService {
func fetchFeed() async throws -> [Post]
}
나쁜 서비스 특성
- UI 상태를 보유
- 뷰모델을 변형
- 네비게이션 트리거
- 알림 표시
서비스는 오직 작업만 수행해야 합니다.
상태 기반 네비게이션
@Observable
class AppState {
var route: AppRoute?
}
.onChange(of: appState.route) { route in
navigate(route)
}
이렇게 하면 네비게이션이 예측 가능하고 테스트 가능해집니다.
위험한 패턴
ChildView(value: $viewModel.someState) // ⚠️
권장 패턴
ChildView(onChange: viewModel.childChanged)
바인딩은 UI 구성용이며, 아키텍처 경계를 넘는 용도로 사용하면 안 됩니다.
테스트가 간단해짐
func test_like_updates_state() async {
let vm = FeedViewModel(service: MockService())
await vm.load()
vm.likeTapped(postID: "1")
XCTAssertTrue(vm.posts.first!.liked)
}
UI가 필요하지 않습니다.
체크리스트
“이 파일을 읽었을 때, 누가 상태를 소유하고 있는지 알 수 있나요?”
답이 불분명하면 → 아키텍처가 잘못된 것입니다.
단방향 데이터 흐름의 장점
- 예측 가능한 업데이트
- 버그 감소
- 비동기 처리 용이
- 테스트 간소화
- 확장 가능한 아키텍처
- 관심사의 명확한 분리
SwiftUI는 이 모델을 위해 설계되었습니다 — 이를 받아들이면 모든 것이 자연스럽게 맞아떨어집니다.