Swift의 Combine 프레임워크를 쓰다 보면,
@Published와 CurrentValueSubject 중 뭘 써야 할지 헷갈릴 때가 많다.
특히 UIKit 기반 MVVM에서 “값이 언제 반영되는가?”는 아주 중요하다.
두 개의 차이는 바로 업데이트 타이밍(willSet vs didSet) 이다.
🔍 1️⃣ 공통점부터 이해하자
두 개 모두 “값이 변할 때마다 구독자에게 이벤트를 발행하는 Publisher”다.
| 공통점 | 설명 |
| 데이터 스트림 발행 | 값이 바뀔 때마다 새로운 이벤트를 내보냄 |
| 구독(sink/assign) 가능 | 외부(ViewModel, View)가 변화를 감지 |
| 현재 값 + 변경 이벤트 관리 | 마지막 값을 저장하고, 새 값이 생기면 전파 |
즉, 둘 다 Combine의 핵심 개념인 “반응형 데이터 흐름”을 구현하기 위한 도구다.
그런데 내부 동작 시점이 다르다.
⚙️ 2️⃣ 핵심 차이 정리
| 구분 | @Published | CurrentValueSubject |
| 이벤트 발생 시점 | willSet 시점 (값이 완전히 바뀌기 전) | didSet 시점 (값이 바뀐 후) |
| 이벤트 제어 방식 | 자동으로 발행 ($propertyName) | 명시적으로 .send() 호출 |
| 초기값 | 프로퍼티 선언 시 자동 설정 | 생성 시 직접 지정해야 함 |
| 쓰임새 | ViewModel / SwiftUI용 | Store / UIKit / 테스트용 |
| 제어 가능성 | 낮음 (자동 발행) | 높음 (직접 발행 시점 제어 가능) |
👉 즉, @Published는 값이 변경되기 직전에 이벤트를 날리고,
CurrentValueSubject는 이미 변경된 이후에 이벤트를 보낸다.
🧠 3️⃣ 예시로 보는 차이
@Published 사용
@Published private var diaries: [EmotionDiaryModel] = []
func save(_ diary: EmotionDiaryModel) {
diaries.append(diary)
}
이 경우 Combine 이벤트($diaries)는 내부적으로 willSet 시점에 발행된다.
즉, 구독자가 받을 때는 값이 완전히 바뀌기 전의 상태일 수 있다.
➡️ 대부분의 상황에서는 큰 문제가 없지만,
빠르게 CRUD를 반복하거나 UI 업데이트 타이밍이 중요한 경우
한 프레임 늦게 반영될 수 있다.
CurrentValueSubject 사용
private let diariesSubject = CurrentValueSubject<[EmotionDiaryModel], Never>([])
func save(_ diary: EmotionDiaryModel) {
var current = diariesSubject.value
current.append(diary)
diariesSubject.send(current) // ✅ 변경된 이후 직접 발행
}
이 경우엔 이미 값이 바뀐 후 .send()를 호출하므로,
항상 최신 상태를 구독자(ViewModel, View)가 즉시 받는다.
즉, 실시간 업데이트가 중요한 앱에서는 CurrentValueSubject가 더 예측 가능하다.
📱 4️⃣ 감정일기(EmotionDiary) 앱 예시로 이해하기
구조 요약
DiaryStore → (Publisher)
↓
DiaryViewModel → (Combine 구독)
↓
DiaryViewController → (UI 반영)
| 동작 | @Published 결과 | CurrentValueSubject 결과 |
| 첫 번째 저장 | 변경 전 값이 전파될 수 있음 | 변경된 값 즉시 전파 |
| 빠른 연속 저장 | 이벤트 지연 발생 가능 | 항상 최신 상태 유지 |
| View 즉시 반응 | 가끔 한 프레임 늦게 반응 | 즉시 반응 |
➡️ 즉, “감정일기 작성 → 바로 리스트 업데이트” 같은 구조에서는
CurrentValueSubject가 더 안정적이고 반응성이 좋다.
📈 5️⃣ 흐름 그림으로 보면
@Published:
willSet → 이벤트 발행
↓
값 변경
↓
View가 다음 RunLoop에서 반응
CurrentValueSubject:
값 변경
↓
.send() 호출
↓
즉시 이벤트 발행 (최신 값)
✅ 6️⃣ 결론
| 상황 | 권장 | 선택 이유 |
| SwiftUI / ObservableObject | ✅ @Published | 자동 바인딩, 코드 간결 |
| UIKit + MVVM | ✅ CurrentValueSubject | 이벤트 타이밍 제어 가능, 항상 최신 상태 보장 |
| 테스트 코드 / Mock Data | ✅ CurrentValueSubject | .send()로 값 주입이 용이 |
🔹 @Published는 값이 바뀌기 전(willSet) 이벤트를 보내서,
연속 CRUD 시 살짝 늦게 반영될 수 있다.
🔹 CurrentValueSubject는 값이 바뀐 후(didSet) 직접 발행하므로,
UIKit 환경에서 실시간 반응형 로직을 구현할 때 더 안정적이다.
🧩 7️⃣ UIKit용 예시: DiaryStore (CurrentValueSubject 기반)
아래는 UIKit + MVVM 환경에서 실시간으로 데이터 변화를 감지할 수 있는
DiaryStore 예제 코드다.
import Foundation
import Combine
/// 감정일기 데이터를 중앙에서 관리하는 Store (UIKit MVVM 버전)
final class DiaryStore: DiaryProviding {
// MARK: - Publisher
private let diariesSubject = CurrentValueSubject<[EmotionDiaryModel], Never>([])
/// 외부에서 구독 가능한 Publisher
var diariesPublisher: AnyPublisher<[EmotionDiaryModel], Never> {
diariesSubject.eraseToAnyPublisher()
}
/// 현재 스냅샷 (즉시 접근용)
var snapshot: [EmotionDiaryModel] {
diariesSubject.value
}
// MARK: - CRUD
@discardableResult
func save(_ diary: EmotionDiaryModel) -> Bool {
var current = diariesSubject.value
current.append(diary)
current.sort { $0.createdAt > $1.createdAt }
diariesSubject.send(current) // ✅ didSet 시점 — 변경 후 이벤트 발행
return true
}
@discardableResult
func update(_ diary: EmotionDiaryModel) -> Bool {
var current = diariesSubject.value
if let index = current.firstIndex(where: { $0.id == diary.id }) {
current[index] = diary
current.sort { $0.createdAt > $1.createdAt }
diariesSubject.send(current)
return true
}
return false
}
@discardableResult
func delete(id: UUID) -> Bool {
var current = diariesSubject.value
current.removeAll { $0.id == id }
diariesSubject.send(current)
return true
}
}
이렇게 하면 ViewModel에서 store.diariesPublisher를 구독하고,
ViewController에서 TableView를 갱신하는 구조로
SwiftUI처럼 반응형 업데이트가 가능하다.
🚀 마무리
UIKit 환경에서 Combine을 쓸 때는
“값이 언제 반영되는가?”가 꽤 중요한 요소다.
SwiftUI는 @Published가 자연스럽게 맞지만,
UIKit에서는 CurrentValueSubject가 더 안정적이고 예측 가능하다.
'감정일기(가칭)' 카테고리의 다른 글
| 🧭 DiaryProviding 프로토콜 — 읽기 전용 인터페이스로 ViewModel을 보호하기 (0) | 2025.10.23 |
|---|---|
| 🍋 DiaryStore 설계 총정리 — “하나의 진실(Single Source of Truth)” 패턴으로 Core Data 관리하기 (0) | 2025.10.23 |
| 🧩 LemonLog Core Data 테스트 콘솔 로그 분석 (0) | 2025.10.21 |
| 🚀 Xcode에서 Core Data CRUD 테스트 실행하기 (0) | 2025.10.21 |
| 🧠 Core Data CRUD 테스트 자동화 — DiaryTestManager 완전 해설 (0) | 2025.10.21 |