길었던 Combine의 마지막 게시글..!
사용자 정의 연산자를 만들기 전에, Subscriber
의 작동 방식과 Publisher
의 작동 방식을 다시 한 번 복습해보겠슴니다.
Subscriber
subscribe
를 호출하여 퍼블리셔에 구독됨니다.Publisher
receive(subscriber:)
를 구현함니다.subscriber.receive(subscription:)
을 호출함니다.Subscription
request(_:)
를 구현하며, 구독은 값을 생성하고 구독자의 receive(_:)
를 호출하여 응답합니다.receive(completion:)
를 호출할 책임이 있슴니다. cancel
을 구현해야함니다.이제 연산자는 어떨까요 ? 퍼블리셔와 구독자 사이에 연산자를 삽입한다고 가정하면 오퍼레이터는 알아야 할 세 가지 사항이 있슴니다.
처음 흐름을 살펴보면, 파이프라인이 조립되면서 각 오퍼레이터는 업스트림의 매개변수를 받는 이니셜라이저를 호출하여 인스턴스화 됩니다. 이제 각 오퍼레이터는 누가 자신의 업스트림인지 알 수 있습니다. 하지만 구독이 일어나지 않았으므로, 맨 위에 있는 퍼블리셔는 아직 아무 일도 하지 않슴니다.
맨 아래에서 바로 위에 있는 오퍼레이터를 구독한다고 할 때, 오퍼레이터는 내부 클래스의 구독을 위에서 받도록 제어 흐름을 업스트림으로 전달함니다. 최상위 퍼블리셔를 도달하면, 구독자인 이너 클래스의 인스턴스를 생성하고 receive(subscription:)
를 다운스트림으로 호출합니다. 오퍼레이터는 이 동작을 업스트림쪽으로 호출합니다. 업스트림 객체에서 구독을 매개변수로 사용하고 호출하여 업스트림에 구독합니다.
Publisher
Operator: Inner ⬆️ subscribe
Operator: Inner ⬆️ subscribe
Subscriber ⬆️ subscribe
Publisher: Inner ⬇️ receive
(Operator) Inner ⬇️ receive
(Operator) Inner ⬇️ receive
Subscriber
최상위 퍼블리셔에 도달하고, 구독 클래스의 인스턴스를 생성하고 다운스트림으로 돌아가서, 구독자에 대해 receive(subscription:)
를 호출함니다. 이 구독자는 오퍼레이터의 내부 클래스이고, 이 자체가 구독의 역할도 하므로 업스트림에서 받은 구독을 보유하고 있다가 아래의 구독자에 의해 receive(subscription:)
가 호출 되면 자기 자신을 아래로 전달함니다.
이제 오퍼레이터는 체인의 위쪽과 아래쪽을 모두 볼 수 있습니다.
Subscription
⬆️ upstream
(Operator) Inner
⬇️ downstream
Subscriber
이제 양방향으로 작동하는 체인이 생겼슴니다. 따라서, 오퍼레이터가 파이프라인 위쪽으로 또는 아래쪽으로 메시지를 전달 할 수 있습니다.
예를 들어, 최종 구독자가 구독을 받았다고 할 때, 이 경우 일반적으로 request
를 호출하고 파이프라인을 따라 연쇄 반응이 일어납니다. 연쇄 반응의 맨 아래에 있는 구독이 상위 구독에 request
를 호출하여, 퍼블리셔의 구독에 요청이 호출될 때까지 계속 반복됩니다.
이제 DoNothing
이라는 연산자를 하나 만들어보겠습니다. 이 연산자는 아무 일도 하지 않고 이벤트를 전달만 하는 역할이 전부입니다.
struct DoNothing<Upstream: Publisher>: Publisher {
typealias Output = Upstream.Output
typealias Failure = Upstream.Failure
let upstream: Upstream
init(upstream: Upstream) {
self.upstream = upstream
}
// 구독할 때 upstream의 subscribe를 호출
func receive<S>(subscriber: S)
where S : Subscriber, S.Input == Output, S.Failure == Failure {
self.upstream.subscribe(Inner(downstream:subscriber))
}
// ... Inner goes here ...
}
이제 DoNothing
의 구독이자, 구독자의 역할을 하는 Inner
클래스를 만들어보겠습니다.
class Inner<S:Subscriber, Input>: Subscriber, Subscription
where S.Failure == Failure, S.Input == Input {
var downstream: S?
var upstream: Subscription?
init(downstream: S) {
self.downstream = downstream
}
// 구독자 프로토콜을 만족하는 메서드 3가지
// keep subscription, pass _self_ downstream
func receive(subscription: Subscription) {
self.upstream = subscription
self.downstream?.receive(subscription: self)
}
// pass input downstream
func receive(_ input: Input) -> Subscribers.Demand {
return self.downstream?.receive(input) ?? .max(0)
}
// pass completion downstream
func receive(completion: Subscribers.Completion<Failure>) {
self.downstream?.receive(completion: completion)
self.downstream = nil
self.upstream = nil
}
// 구독 프로토콜을 만족하는 메서드 2가지
// pass demand upstream
func request(_ demand: Subscribers.Demand) {
self.upstream?.request(demand)
}
// pass cancel upstream
func cancel() {
self.upstream?.cancel()
self.upstream = nil
self.downstream = nil
}
}