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

Combine의 .assign vs .sink

by 밤새는 탐험가89 2025. 12. 16.
728x90
SMALL

언제 쓰고, 왜 다른가?

Combine을 쓰다 보면 가장 자주 마주치는 구독 연산자가 바로
.assign 과 .sink다.

겉으로 보면 둘 다 Publisher를 구독하는 것처럼 보이지만,
의도·역할·사용 위치는 명확하게 다르다.

 

1️⃣ 한 줄 요약부터 

.assign은 “상태를 연결하는 도구”이고,
.sink는 “행동을 정의하는 도구”다.

 

이 차이를 이해하면
“이 상황에서 왜 assign이고, 왜 sink인지”가 자연스럽게 보인다.

 

2️⃣ .assign — 자동 상태 바인딩

✔️ 정의

publisher
    .assign(to: \.property, on: object)

 

Publisher가 방출한 값을
지정된 객체의 프로퍼티에 자동으로 대입한다.

✔️ 왜 “자동”이라고 부르나? 

.assign은 내부적으로 이런 코드를 대신 써주는 것과 같다:

publisher.sink { value in
    object.property = value
}

 

즉,

  • 클로저 ❌
  • 조건 분기 ❌
  • 로직 개입 ❌
    그냥 값 전달만 함

 

✔️ 예제: ViewModel 상태 갱신

store.diariesPublisher
    .map { $0.filter { $0.isToday } }
    .assign(to: \.diaries, on: self)
    .store(in: &cancellables)

 

이 코드의 의미는 명확하다:

“일기 목록이 바뀌면
오늘 날짜에 해당하는 일기만 골라서
diaries 상태로 유지해라”

 

여기서 ViewModel은
“상태의 소유자” 역할만 한다.

 

✔️ 언제 .assign을 쓰는가?

조건은 딱 하나다.

“Publisher의 결과가
어떤 프로퍼티의 상태가 될 때

 

대표적인 예:

  • a@Published 상태 업데이트
  • ViewModel의 SSOT 유지
  • UI에 직접 영향 주는 데이터

✔️ 주의사항

1️⃣ 에러를 처리할 수 없다

.assign(...)

 

→ Failure == Never 인 Publisher만 가능
에러 처리 필요하면 sink 사용

 

 2️⃣ 부작용을 넣으면 안 된다 

.assign { value in
    print(value) // ❌ 이런 거 못 함
}

 

assign은 “상태 연결”만 담당

 

3️⃣ .sink — 모든 행동의 출입구

publisher.sink(
    receiveCompletion: { ... },
    receiveValue: { ... }
)

 

Publisher가 방출한 이벤트에 대해
개발자가 직접 무엇을 할지 정의

 

 

✔️ 왜 .sink는 “자동이 아닌가?”

.sink는 아무것도 자동으로 하지 않는다.

publisher.sink { value in
    // 여기서 아무것도 안 쓰면
    // 진짜 아무 일도 안 일어남
}

 

  • 상태 업데이트도
  • 네비게이션도
  • 로그도

👉 전부 개발자가 직접 작성

 

✔️ 예제: 부작용 처리

viewModel.saveResultPublisher
    .sink { result in
        switch result {
        case .success:
            self.showToast("저장 완료")
        case .failure(let error):
            self.showError(error)
        }
    }
    .store(in: &cancellables)

 

이건 상태가 아니라:

  • 알림 표시
  • 화면 전환
  • 에러 처리

같은 행동(Side Effect) 이다.

 

✔️ 언제 .sink를 쓰는가?

“값을 받아서
무언가를 ‘한다’

 

대표적인 경우:

  • Alert / Toast
  • Navigation
  • Analytics / Logging
  • 에러 처리
  • 여러 프로퍼티 동시 업데이트

✔️ 주의사항

1️⃣ retain cycle 주의

.sink { [weak self] value in
    self?.doSomething()
}

assign은 안전하지만
sink는 항상 self 캡처 주의

 

2️⃣ ViewModel이 비대해지기 쉬움

로직 + 상태 + UI 행동을
전부 sink에 넣기 시작하면 구조가 무너진다.

728x90
LIST