본문 바로가기
감정일기(가칭)

🧩 @Published vs CurrentValueSubject — Combine에서의 타이밍 차이 완벽 정리

by 밤새는 탐험가89 2025. 10. 22.
728x90
SMALL

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가 더 안정적이고 예측 가능하다.

728x90
LIST