[Combine] Operators - 07

rbw·2024년 1월 5일
0

Combine

목록 보기
11/11

길었던 Combine의 마지막 게시글..!

Custom Operators

사용자 정의 연산자를 만들기 전에, Subscriber의 작동 방식과 Publisher의 작동 방식을 다시 한 번 복습해보겠슴니다.

Subscriber

  • 구독자는 구독자를 매개변수로 사용하여 퍼블리셔에서 subscribe를 호출하여 퍼블리셔에 구독됨니다.
  • 아래 메서드를 구현함니다.
  • receive(subscription:): 퍼블리셔가 처음에 구독 객체를 넘겨주면 구독자가 이를 보유합니다.
  • receive(_ :): 업스트림에서 값이 도착할 때 사용합니다.
  • receive(completion:): 업스트림에서 완료가 도착할 때 사용합니다.

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
	}
}
  • 구독자로서 이너클래스는 업스트림에서 도착하는 것을 받아 다운스트림으로 전달 하는 것이 하나의 역할입니다.
  • 구독으로서 이너클래스는 다운스트림에서 도착한 것을 수신하여 업스트림으로 전달하는 역할을 합니다.
profile
hi there 👋

0개의 댓글