[SwiftUI] View modifier(onChange, onReceive...
2026. 2. 4. 18:15ㆍ🍏/Swift
View modifier
| 계열 | 대표 API | 트리거 기준 |
| 값 변화 | onChange | SwiftUI 상태 비교 |
| 이벤트 | onReceive | Publisher emit |
| 생명주기 | onAppear | View 등장 |
| 비동기 | task(id:) | id 변경 |
| 앱 상태 | scenePhase | 시스템 상태 |
| 입력 | onSubmit | 유저 입력 |
| 레이아웃 | Geometry / Preference | 레이아웃 변화 |
| 제스처 | onTap / gesture | 사용자 액션 |
뷰 모디파이어는 위와 같은 API들이 있으며, 이번 포스팅에서는 onChange, onReceive에 대해서 알아보려고 합니다.

onChange의 개념 (SwiftUI 값-기반 변화 감지)
- 트리거 기준: “이 뷰가 업데이트되는 과정에서” 관찰 중인 value를 다시 읽었을 때, 직전 값과 !=(Equatable 비교) 이면 실행.
- “값(value)의 변화”에 반응하는 훅
무엇을 감지하나?
- of:로 넘긴 값 자체의 “이전 값 vs 현재 값”을 비교해서,
- 달라졌을 때만 action을 실행합니다. (Equatable 기반)
언제 실행되나? (핵심)
- 값이 바뀌는 “순간”에 즉시 실행되는 게 아니라,
- SwiftUI가 뷰를 업데이트(=body 재평가)하는 흐름 안에서
- 새 값을 읽고
- 이전 값과 비교한 뒤
- 다르면 실행합니다.
그래서 더 정확한 개념은:
onChange는 “값이 바뀌면”이 아니라 “값이 바뀐 상태로 SwiftUI가 그 변화를 관측할 기회가 생겼을 때” 실행됩니다.
왜 이렇게 설계됐나?
SwiftUI는 UI를 “명령형으로 이벤트 처리”하는 방식이 아니라,
- 상태(state)가 바뀌면
- 그 결과로 View가 다시 계산되고
- UI가 그려지는 구조입니다.
onChange는 이 흐름에 맞춰서 “상태 변화 → 사이드이펙트”를 연결하는 도구입니다.
초기 실행은?
- iOS 17+의 onChange(of:initial:)에서
- initial: false(기본) → 처음 나타날 때는 실행 안 함
- initial: true → 처음 나타날 때도 1회 실행
장점
- 같은 값 반복이면 실행 안 함 (중복 억제)
- 값 기반이라 의도가 명확함 (“이 값이 바뀌면 해야 하는 일”)
주의점 (개념적으로 꼭 알아야 하는 2가지)
- 이 뷰가 그 값을 “관찰하는 구조”여야 안정적
- @State, @Binding, @ObservedObject/@StateObject, @Environment 등
- → 값 변경이 뷰 업데이트로 이어지는 연결고리가 있어야 함
- 값이 큰 타입이면 비교 비용이 커질 수 있음
- 큰 배열/큰 struct를 그대로 넣으면 ==가 무거워질 수 있음
더 정확한 문장
value 자체가 바뀌는 순간에 바로 실행되는 게 아니라, 그 값이 바뀐 상태로 SwiftUI가 뷰 업데이트를 수행하면서 이전 값과 비교해 “바뀌었네?”가 확인되는 시점에 실행된다.
즉 onChange는 SwiftUI의 업데이트 사이클에 종속돼요. (뷰가 업데이트를 안 하면 비교도 안 하고 실행도 안 함)
- 중복 호출: 같은 값이면 실행 안 함(Equatable 비교니까).
- 초기 실행: 기본은 처음 등장 때는 실행 안 함. iOS 17+의 initial: true를 켜면 1회 실행.

onReceive의 개념 (Combine 이벤트-기반 수신)
- 트리거 기준: publisher가 emit(방출) 할 때마다 실행.
- SwiftUI 업데이트/Equatable 비교 같은 건 상관없고, 이벤트가 오면 무조건 실행(값이 같아도 실행).
- “이벤트(event stream)”에 반응하는 훅
무엇을 감지하나?
- Combine Publisher가 emit하는 이벤트를 감지합니다.
- 이벤트가 오면 매번 action 실행 (값이 같아도 실행)
언제 실행되나?
- 뷰가 화면에 존재하면서 publisher를 구독하고 있는 동안
- publisher가 emit하면 실행됩니다.
- SwiftUI의 body 업데이트와는 독립적입니다.
즉:
onReceive는 “뷰 상태 변화”가 아니라 “외부에서 날아오는 이벤트 스트림”을 받아서 처리하는 도구다.
“처음 등록될 때 1회는 무조건 실행”은 맞나?
반은 맞고 반은 틀려요.
- onReceive 자체가 그런 규칙이 있는 게 아니라,
- “publisher 종류”에 따라 다릅니다.
구독하자마자 값 1회 주는 애들
- CurrentValueSubject
- @Published의 publisher ($value)
- → “현재 값”을 들고 있어서 구독 즉시 한 번 보내는 성향이 흔함
구독해도 아무것도 안 주는 애들
- PassthroughSubject
- → send()가 오기 전까지는 아무 이벤트도 없음
타이머
- Timer.publish(...).autoconnect()
- → 즉시 1회가 아니라 “틱 주기”에 따라 들어옴
장점
- 외부 이벤트를 UI에 연결하기 좋음
- 뷰 업데이트에 의존하지 않음 (스트림이면 스트림대로 받음)
주의점 (여기가 퍼포먼스/버그의 핵심)
- 이벤트 빈도 = 호출 빈도
- 고빈도 스트림이면 action이 폭주할 수 있음
- → removeDuplicates, throttle, debounce 같은 제어가 필요할 때가 많음
- 스레드/액터 컨텍스트
- publisher가 백그라운드에서 emit하면 action도 그쪽에서 불릴 수 있음
- UI 상태 변경은 main에서 해야 안전
- → 필요하면 receive(on:)로 메인에 올림
선택 기준을 한 줄로 정리하면
- 내가 감지하려는 게 “값의 변화”면 → onChange = “렌더링 중 값이 바뀐 걸 발견하면 반응”
- 내가 감지하려는 게 “이벤트 스트림”이면 → onReceive = “이벤트 스트림이 오면 즉시 반응”
“원천이 Combine Publisher라면 onReceive가 자연스럽고, SwiftUI 상태/바인딩/환경값이라면 onChange가 자연스럽다.”
짧은 예시
✅ onChange가 딱 맞는 예
@State var isOn: Bool
Toggle("Auto Upload", isOn: $isOn)
.onChange(of: isOn) {
// 토글이 바뀌면 설정 저장
}
✅ onReceive가 딱 맞는 예
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
// 앱이 active 될 때마다 새로고침
}
'🍏 > Swift' 카테고리의 다른 글
| [Swift] Dispatch / Static Dispatch / Dynamic Dispatch (0) | 2026.02.11 |
|---|---|
| [iOS / macOS] 내 맥북에서 서버를 만들고 내 아이폰과 소켓 통신을 해보자 (0) | 2024.12.31 |
| [iOS] 데이터를 저장하는 방법들 / 간단 예제 (0) | 2024.12.31 |
| [iOS] 에러 처리 / Error Handling (0) | 2024.12.30 |
| [Swift] 접근 제어자(Access Control Levels) (0) | 2024.12.29 |