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

📘 MVVM에서 PassthroughSubject로 화면 이동 이벤트 처리하기

by 밤새는 탐험가89 2025. 11. 24.
728x90
SMALL

MVVM 패턴을 사용하면서 가장 많이 고민하는 부분 중 하나는 바로 “화면 이동을 어디서 처리해야 하는가?” 입니다.

ViewController에서 직접 화면 이동을 처리하면 MVVM 구조가 흐트러지고

ViewModel이 화면 이동 코드를 가지고 있으면 역할이 꼬여버립니다.

그럼 화면 이동은 누구 책임일까요?


정답은:

 

화면 이동은 ViewController가 처리하되,

ViewModel이 “언제 이동해야 하는지”는 명확하게 알려줘야 한다.

 

이걸 깔끔하게 해결하는 방법이 바로 PassthroughSubject를 이용한 “이벤트 기반 화면 이동 처리”입니다.

 

이 글에서는 PassthroughSubject가 무엇인지, 왜 필요한지,
그리고 실제 코드 예시까지 초보자도 바로 따라 할 수 있게 설명합니다.


🟦 1. PassthroughSubject란 무엇인가?

Combine에서 제공하는 Publisher의 한 종류입니다.

 

📌 한 줄로 정의

특정 시점에 “이벤트”를 ViewController에 전달하는 용도.

Published가 “상태(state)”의 변화를 알리는 용도라면,
PassthroughSubject는 “이벤트(event)”를 알리는 용도입니다.

 

🔹 Published가 사용하는 상황

1. 텍스트 변경

2. 리스트 업데이트

3. UI에 표시되는 값이 바뀜

 

🔹 PassthroughSubject가 사용하는 상황

1. 화면 이동

2. alert 띄우기

3. toast 표시

4. 특정 버튼 클릭 이벤트 전파

 

즉, Published는 “보여줄 데이터”,
Subject는 “한 번 발생하는 행동”을 전달합니다.


🟦 2. 왜 화면 이동에 PassthroughSubject를 쓰는가?

기존 방식의 문제점은 이렇습니다:

 

1. ViewController가 Published를 구독하다

2. 특정 타이밍에 push 화면 이동을 “VC가 판단”해야 함

3. 이렇게 되면 VC가 너무 많은 역할을 하게 되고

4. ViewModel이 화면 이동의 의도를 명확하게 전달하지 못함

 

즉,

 

MVVM에서 가장 나쁜 패턴은 VC가 ViewModel의 상태를 보고

화면 이동 타이밍까지 스스로 판단하는 구조입니다.

 

PassthroughSubject를 사용하면:

1. ViewModel은 “이동해야 하는 때”를 정확히 알려주고

2. ViewController는 “이동만” 처리하면 됩니다

역할 분리가 매우 명확해지므로 구조가 깔끔해집니다.


🟦 3. 실제 코드 예시로 완벽 이해하기

① ViewModel: 화면 이동 이벤트 선언

class HomeViewModel {
    let showWeeklySummary = PassthroughSubject<[EmotionDiaryModel], Never>()
}

 

이제 ViewModel은 “이동해야 해!”라는 신호를 보낼 준비가 되었습니다.

 

② ViewModel: 특정 로직 뒤에 이벤트 발행(send)

예: 주간 날짜를 탭하면 그 주의 감정일기 목록을 계산한 뒤 화면 이동 필요

func didSelectWeeklySummary(_ weekDates: [DiaryCoreDataManager.Weekday: Date]) {
    // 1) 주간 데이터 계산
    let sorted = weekDates.sorted { $0.key.rawValue < $1.key.rawValue }
    guard let firstDate = sorted.first?.value else { return }
    
    let result = fetchDiariesWeek(from: firstDate)

    // 2) 이제 화면 이동해도 좋아! 라는 이벤트를 VC에 전달
    showWeeklySummary.send(result)
}

 

③ ViewController: 이벤트 구독 후 화면 push

homeVM.showWeeklySummary
    .receive(on: RunLoop.main)
    .sink { [weak self] diaries in
        let vc = HomeEmotionSummaryViewController(diariesFromWeek: diaries)
        self?.navigationController?.pushViewController(vc, animated: true)
    }
    .store(in: &cancellables)

 

이제 ViewController는 데이터가 준비되면 자동으로 화면 이동을 실행합니다.
VC는 “이동만” 담당하는 매우 단순한 구조가 됩니다.


🟦 4. PassthroughSubject의 장점 5가지

✔ 1) ViewModel과 ViewController의 책임이 명확하게 분리됨

ViewModel → 화면 이동 “신호”만 보냄
ViewController → 실제 push/pop만 처리

 

✔ 2) 화면 이동 로직이 ViewModel의 흐름에 자연스럽게 녹아듦

데이터 로직과 네비게이션 타이밍이 정확히 맞음

 

✔ 3) 복잡한 조건에서도 UI 코드가 깔끔해짐

VC는 “이동 신호”만 받으면 됨

 

✔ 4) 테스트하기 쉬워짐

ViewModel의 이벤트만 검증하면 됨

 

5) 대규모 앱에서 유지보수성이 매우 높아짐

화면 이동이 ViewModel 상태에 종속되지 않기 때문

728x90
LIST