언제 쓰고, 왜 다른가?
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에 넣기 시작하면 구조가 무너진다.
'감정일기(가칭)' 카테고리의 다른 글
| Combine에서 .sink와 .assign을 “상태 관리”에 쓰는 2가지 방식 (0) | 2025.12.17 |
|---|---|
| 빈 상태 UI(Placeholder)와 Combine 상태 설계 .assign에서 .sink로 전환해야 하는 정확한 순간 (0) | 2025.12.16 |
| LemonLog: isSameDay와 bind()로 날짜별 일기 리스트 만들기 (Combine + @MainActor) (0) | 2025.12.16 |
| 📘 diariesPublisher vs fetchDiaryByDay (0) | 2025.12.16 |
| 📅 날짜 선택 → 감정일기 리스트 화면 설계하기 (LemonLog 사례) (0) | 2025.12.16 |