| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- xcode 공백 표시
- swiftdata
- 테스트 타겟
- png
- NSTextStorage
- swift 모듈화
- 캐러셀
- github 시작하기
- fetchdescriptor
- .pbxproj
- xcode 엔터 표시
- heic
- TestFlight
- contentalignmentpoint
- SwiftUI
- 코드스쿼드
- Firestore
- Cocoa Pod
- spm 에러
- NSTextStorageDelegate
- JPG
- 클린 아키텍처
- 무한스크롤
- nidthirdpartylogin
- 타뷸레이션
- 함께자라기
- JPEG
- webp
- 팀 개발을 위한 git
- Tuist
- Today
- Total
Sure, Why not?
SwiftUI ) @Published는 어떻게 동작하는가 - objectWillChange 본문

ObservableObject는 주로 뷰모델에서 사용하고,
뷰에서 사용할 프로퍼티 변화를 관찰하기 위해 @Published를 자연스럽게 써왔다.
그러던 중 프로젝트에서 @Published 프로퍼티가 업데이트되는 과정을 디버깅하다가
구독자로 전달되는 값과 실제 프로퍼티 값이 서로 다르게 보이는 상황을 마주했다.
그동안은 단순히 뷰에 반영할 프로퍼티 변화를 알리는 용도라고만 생각하며 사용했지만,
이번 경험을 계기로 @Published가 내부에서 어떤 방식으로 값 변경을 알리는지 궁금해졌다.
그래서 ObservableObject와 @Published의 퍼블리싱 과정 전반을 정리해 보고자 한다.
import Combine
final class CounterViewModel: ObservableObject {
@Published var count: Int = 0
private var cancellables = Set<AnyCancellable>()
init() {
$count
.sink { newValue in
print("sink에서 받은 값:", newValue)
print("실제 프로퍼티 값:", self.count)
}
.store(in: &cancellables)
}
func increase() {
count += 1
}
}
@Published의 퍼블리싱 타이밍을 확인하기 위한 간단 예시이다.

increase를 호출하여 count 값을 변경했을 때 다음과 같은 로그가 출력된다.
해당 상황이 낯설게 느껴졌기 때문에, 정확한 원리를 이해하지 못하면 앞으로도 혼란이 올 것 같아서
정리의 필요성을 느끼게 되었다.
ObservableObject
ObservableObject | Apple Developer Documentation
A type of object with a publisher that emits before the object has changed.
developer.apple.com
사실 공식 문서를 보면 바로 정답이 있다.
기본적으로 ObservableObject는 objectWillChange publisher를 자동생성한다.
이 퍼블리셔는 @Published 프로퍼티가 실제로 변경되기 직전에 변경될 값을 방출한다고 나타나 있다.
ObservableObject 는 SwiftUI에서 뷰가 관찰할 수 있는 객체를 만들기 위한 프로토콜로써,
클래스 기반으로 동작이 된다.
뷰는 객체의 내부 상태 변화를 감지하고 뷰 업데이트가 된다.
@Published
@propertyWrapper
struct Published<Value>
프로퍼티에 자동으로 기능 넣어주는 프로퍼티 래퍼로써,
프로퍼티에 @Published 속성을 적용하면, 퍼블리셔가 생성이 된다.
프로퍼티가 바뀌면 새로운 값을 자동으로 스트림으로 내보내고,
뷰는 해당 퍼블리셔를 감지해서 뷰를 업데이트를 하는 과정을 가지게 된다.
만약에 @Published var value: Int를 선언하면 Swift는 내부적으로 다음 두 가지를 만든다:
- value: 저장 프로퍼티
- $value: Published<Int>.Publisher

이때 중요한 포인트는
퍼블리싱은 willSet에서 발생한다.
1. 프로퍼티에 새로운 값이 적용되기 직전에
2. 구독자는 변경될 새로운 값을 먼저 받는다.
3. 하지만 실제 저장 프로퍼티에는 아직 이전 값이 남아 있는 상태이고, 이후에 적용된다.
그래서 단순히 관찰했을때, willSet 시점에서 새로운 값을 넘겨받지만,
해당 시점에 프로퍼티 값이 실제로 바뀌지는 않았기 때문에
위의 출력된 로그를 확인할 수 있었다.
@Published 없이 구현 한다면?
결국 @Published는 너무 자연스럽게 사용되는 속성이지만,
뷰를 업데이트하는 기본 원리는 단순하다.
아래처럼 사용하지 않고,
willSet에서 objectWillChange.send()만 호출해도
뷰는 정상적으로 업데이트된다. -> 뷰에게 업데이트하라고 신호를 보내기 때문!
final class CounterViewModel: ObservableObject {
var count: Int = 0 {
willSet {
objectWillChange.send()
}
}
func increase() {
count += 1
}
}
@Published가 좋은 이유는 단순히 뷰 업데이트뿐만 아니라
$count처럼 해당 값의 스트림을 제공하는 퍼블리셔를 자동 생성해준다는 점이다.
맨처음 뷰모델을 똑같이 동일하게 구현하려면,
아래처럼 퍼블리셔를 직접 구현해줘야 한다.
final class CounterViewModel: ObservableObject {
let countPublisher = CurrentValueSubject<Int, Never>(0)
var count: Int = 0 {
willSet {
countPublisher.send(newValue)
objectWillChange.send()
}
}
private var cancellables = Set<AnyCancellable>()
init() {
countPublisher
.sink { newValue in
print("sink에서 받은 값:", newValue)
print("실제 프로퍼티 값:", self.count)
}
.store(in: &cancellables)
}
func increase() {
count += 1
}
}

정리
너무 @Published를 단순한 관찰하는 속성 정도로 생각해서 사용하다 보니 낯설게 느껴졌지만,
뷰 업데이트와 퍼블리셔 생성까지 맡는 큰 역할이라는 점을 정리할 수 있었다.
곰곰이 생각해보면 willSet 시점에 신호 보내는 메커니즘도 충분히 자연스러운 구조인데,
정작 로깅할 때 왜 그렇게 어색하게 느껴졌을까 하하하.
'💻' 카테고리의 다른 글
| @State, @Binding (프로퍼티 래퍼 - 2 ) (0) | 2025.12.24 |
|---|---|
| @propertyWrapper 프로퍼티 래퍼란? - 1 (0) | 2025.12.22 |
| UITextView에서 한글 스타일 적용 (1) | 2025.10.13 |
| async let vs TaskGroup vs 연속 await (2) | 2025.08.28 |
| Core Bluetooth (3) | 2025.07.21 |