Combine이라는 개념이 처음 등장한 WWDC19의 Introducing Combine 와Combine in Practice 를 기반으로 전반적인 Combine의 구조를 그려보는 느낌으로 작성된 글임을 참고해주세요!
Customize handling of asynchronous events by combining event-processing operators.
// 이벤트 처리 연산자를 결합하여 비동기 이벤트 처리를 커스터마이징합니다.
라고 써있는데 하단 Overview의 첫문단을 가져와보면.. 다음과 같다.
Q. 그럼 이거 이전에는 불가능했나요?
WWDC에서도 이전방식을 비교하며 도입 배경을 설명하고 있었습니다.

이전에 있었던 비동기 처리방식
결국에는 이전꺼버리고 대체하는것이 아니라, Combine 프레임워크를 통해 더 유연하게 동작할 수 있도록 합니다.
이제 WWDC를 단순히 번역해서 설명하는것은 의미가 많이 퇴색되기도하고, 이해하시는 분도 본점(Apple)에 가서 설명하시는게 더 이상적일 것이라고 생각하기때문에, 해당 내용을 보고 오셨거나, 보기전에 더 이해를 쉽게하기 위한 목적으로 작성을 해보도록하겠습니다.
결국 Combine은 크게 3가지 구성요소로 이루어져있다고 생각하시면 좋습니다.(Subject는 일단 보류)
- 데이터를 뿌리는 Publisher
- 데이터를 받는 Subscriber
- Subscriber가 잘 받을 수 있도록 전달하는 Operator로 이루어져있습니다.

이제 이걸 공식 자료랑 비교하자면..
- subscribe() → 음식점과 주문을 시작
- request(_: Demand) → 필요한 수량 요청
- receive(_: Input) → 주문한 음식 전달
- receive(completion:) → 주문 종료
그런데 이것만으로는 설명이 다 되지않는 것 같습니다. 음식을 배달해주는 Operator가 아직 등장하지 않았기 때문인데, 이걸 설명하기 전에 왜 바로 받기(Publisher가 뿌려준 데이터를 Subscriber입장에서 받기)가 어려운지에 대한 이해가 필요하다.
Q. 왜 Publisher가 뿌린 데이터를 Subscriber가 잘 못받는 경우가 있을까?
이렇게 에러가 뜬다. Publisher.Output이 Int 가 아니다? 라고 오류를 내보내고 있는듯한데, 이거를 이해하기 위해서는 Publisher 의 Output이 무엇인지, 이게 왜 Int를 요구하고 있는지, Publisher와 Subscriber에 대한 이해가 선행되어야할 것이다.
Output과 Failure가 존재하고, subscribe 를 보면 알 수 있듯이, Subscriber의 Input, Failture와 일치해야하는 것이다.
protocol Publisher {
associatedtype Output
associatedtype Failure : Error
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
public protocol Subscriber : CustomCombineIdentifierConvertible {
associatedtype Input
associatedtype Failure : Error
func receive(subscription: Subscription)
func receive(_ input: Self.Input) -> Subscribers.Demand
func receive(completion: Subscribers.Completion<Self.Failure>)
}
예시에서는 Notification Center의 publisher 를 사용할때 Operator가 필요하게 된 상황을 예시로 들고있다.
방금 전에 말한 것처럼, Publisher의 Output, Failure 타입은
Subscriber의 Input, Failure 타입과 정확히 일치해야 한다.
그런데 여기 상황을 보자..!
Notification 을 내보내고 있는데, 받는 쪽은 Int 를 기대하고있다.
(후라이드 치킨을시켰는데, 양념치킨을 주려고 하는 것이다.)
그래서 이 지점에서 Operator가 등장한다.
대단한게 아니라, Publisher와 Subscriber 사이에서 타입을 맞춰주는 통역사 비슷한 것이라고 보면될 것이다.
예시코드에서 map 을 예시로 들고 있으니 같이 살펴보자!

이렇게 map을 통해서 원하는 타입으로 파싱해주고 있는 것을 확인할 수가 있다. Operator 종류는 상당히 많다. 이렇게 원하는 타입으로 반환을 하지 못하는 경우에 이걸 Subscriber에 맞게 가공해주는 연산자로 보시면 편하다.
근데 보니까, 익숙한 함수명들인 것 같다. map, max, flatMap, compactMap, contains, catch, min, first 등등…
Collection 이라면 .. ?
let numbers = [1, 2, 3, 4] let result = numbers .map { $0 * 2 } .filter { $0 > 4 } // [6, 8]
Combine 에서는 ..!
let publisher = numbers.publisher publisher .map { $0 * 2 } .filter { $0 > 4 } .sink { print($0) } // 6, 8 (시간 흐름에 따라 전달)

일단 전체적인 흐름만 잡은 것인데, 아직 설명하지 않은 assign, sink 를 비롯하여, 어떻게 동작하는지에 대한 동작 흐름 설명은 하지 않았는데, 다음 포스팅으로 찾아뵙겠습니다 !