[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🍎 Apple/Combine & Rx

[Combine] Publishing 타이밍 조절하기 (Connectable Publishers)

inu 2023. 10. 27. 00:03
반응형

Controlling Publishing with Connectable Publishers(https://developer.apple.com/documentation/combine/controlling-publishing-with-connectable-publishers)를 의역한 글입니다.


Connectable Publishers

Publisher가 Subscriber에게 Element를 보내기 시작하는 시기를 조정하는 방법

Overview

Publisher가 특정한 동작에 영향을 미치는 속성을 보유하는 경우 Publisher가 Element 생성을 시작하기 전에 Publisher를 구성하려 할 수 있다. 근데 흔히 쓰는 Subscriber인 sink(receiveValue 는 즉시 무제한의 Element를 요청한다.

 

반대로 준비가 되기도 전에 값을 생성하는 케이스도 문제다. 예를 들어 하나의 Publisher에 두개 이상의 subscriber를 세팅하고 싶은데, 구독하면 바로 Element를 요청해버릴 수 있다.

 

예를 들어보자. URLSession.DataTask.Publisher를 생성하고 Subscriber1를 붙인다. 그럼 바로 URL의 Data를 받아오는 작업을 시작할 것이다. 그때 Subscriber2를 붙인다. 만약 Data Task가 Subscriber2가 붙기 전에 마무리 되었다면 Subscriber2는 data를 받지 못하고 completion만 확인하게 될 수 있다.

Hold Publishing by Using a Connectable Publisher

준비전에 publisher의 Element 전송을 막기 위해서 존재하는 것이 ConnectablePublisher protocol이다. Connectable publisher는 connect() 라는 메서드를 호출하기 전가지는 Element를 전송하지 않는다. 만약 실질적으로 값을 생성한 상태이고 아직 만족하지 못한 Demand가 있더라도, connect 메서드가 호출되지 않았다면 Subscriber들에게 어떠한 값도 보내주지 않는다.

 

아래는 위의 상황을 ConnectablePublisher로 극복한 그림이다. connect를 통해 Race condition을 없애고 두 subscriber 모두 값을 받을 수 있도록 보장함을 알 수 있다.

makeConnectable() operator를 통해 ConnectablePublisher를 만들 수 있다. 아래 코드는 위 그림의 상황을 코드로 구현한 것이다.

let url = URL(string: "<https://example.com/>")!
let connectable = URLSession.shared
    .dataTaskPublisher(for: url)
    .map() { $0.data }
    .catch() { _ in Just(Data() )}
    .share()
    .makeConnectable()

cancellable1 = connectable
    .sink(receiveCompletion: { print("Received completion 1: \\($0).") },
          receiveValue: { print("Received data 1: \\($0.count) bytes.") })

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    self.cancellable2 = connectable
        .sink(receiveCompletion: { print("Received completion 2: \\($0).") },
              receiveValue: { print("Received data 2: \\($0.count) bytes.") })
}

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    self.connection = connectable.connect()
}

Use the Autoconnect Operator If You Don’t Need to Explicitly Connect

어떤 Publisher들은 이미 ConnectablePublisher이다. Publisher.Multicast 혹은 Timer.TimerPublisher 가 그 예시이다. 근데 여러 Subscriber 안쓸거라 굳이 connect로 명시해줄 필요없는데 따로 connect() 를 호출하는게 귀찮을 수 있다. 그럴때는 autoconnect() 메서드를 사용하면 된다. 아래같은 예시에서 Timer Publisher는 곧바로 동작한다. 하지만 autoconnect가 없다면 어딘가에서 connect 메서드를 호출해야한다.

 

반응형