[Combine] multicast, share

이정훈·2026년 3월 12일

Combine Framework

목록 보기
7/7
post-thumbnail

두 연산자의 기능은 하나의 Publisher를 여러 Subscriber가 구독할 때, 각 이벤트를 여러번 발행하는 것이 아니라 하나의 동일한 이벤트를 여러 Subscriber에게 전달 할 수 있도록 한다.

일반적으로 Combine의 Publisher는 새로운 구독자가 생길 때마다 새로운 이벤트를 발생 시킨다. 이때, multicastshare를 사용하면 이러한 이벤트를 공유할 수 있다.

왜 사용해야 하는가?

가령, 네트워크 통신과 같은 작업을 하는 Publisher를 세 곳에서 구독하고 있다고 하면, 일반적으로는 구독이 발생하면 3번의 네트워크 요청이 발생할 것이다. 하나의 결과 값을 위해 세 번의 네트워크 요청이 발생하면 자원 낭비로 볼 수 있다. 이러한 작업을 한 번만 수행 후 결과를 여러 곳에서 공유하기 위해 multicastshare를 사용할 수 있다.

아래 예시와 함께 이해 해보도록 하자.

하나의 스트림을 여러 구독자가 구독 하는 경우

import Combine

let publisher = [1, 2, 3, 4, 5].publisher.map { ($0, Int.random(in: 1...100)) }
let cancellable1 = publisher.sink {
    print("cancellable1: \($0)")
}
let cancellable2 = publisher.sink {
    print("cancellable2: \($0)")
}

// cancellable1: (1, 22)
// cancellable1: (2, 57)
// cancellable1: (3, 72)
// cancellable1: (4, 94)
// cancellable1: (5, 63)

// cancellable2: (1, 22)
// cancellable2: (2, 57)
// cancellable2: (3, 72)
// cancellable2: (4, 94)
// cancellable2: (5, 63)

위와 같이 하나의 publisher를 두 번 구독하는 경우, cancellable1으로 값 전달이 모두 끝난 뒤, cancellable2로 값 전달을 다시 시작하게 된다.

multicast 사용

import Combine

let publisher = [1, 2, 3, 4, 5].publisher
    .map { ($0, Int.random(in: 1...100)) }
    .multicast { PassthroughSubject<(Int, Int), Never>() }

let cancellable1 = publisher.sink {
    print("cancellable1: \($0)")
}
let cancellable2 = publisher.sink {
    print("cancellable2: \($0)")
}

publisher.connect()

// cancellable2: (1, 98)
// cancellable1: (1, 98)

// cancellable2: (2, 19)
// cancellable1: (2, 19)

// cancellable2: (3, 53)
// cancellable1: (3, 53)

// cancellable2: (4, 21)
// cancellable1: (4, 21)

// cancellable2: (5, 66)
// cancellable1: (5, 66)

multicast를 사용했을 때는 한 번의 값이 전달 될 때, cancellable1cancellable2에 동시에 전달된 것을 확인할 수 있었다.

또한 multicast를 적용했을 때, 반드시 connect() 메서드를 호출해야 Publisher에서 다운 스트림으로 값이 전달된다.

share 사용

share()multicast와 동일하게 하나의 Publisher에서 여러 Subscriber로 발행된 값을 공유하는 기능을 하는데, 차이점은 share()는 내부적으로 autoconnect()를 호출하여 명시적으로 connect()를 호출하지 않아도 된다는 점이다. 이 차이로 인해 구독 시점을 언제 가져가느냐에 따라 전달 받는 값에 차이가 발생할 수 있어 주의해야 한다.

import Combine
import Foundation

let publisher = [1, 2, 3, 4, 5].publisher
    .map { ($0, Int.random(in: 1...100)) }
    .delay(for: 1, scheduler: DispatchQueue.main)
    .share()

let cancellable1 = publisher.sink {
    print("cancellable1: \($0)")
}
let cancellable2 = publisher.sink {
    print("cancellable2: \($0)")
}

// cancellable1: (1, 98)
// cancellable2: (1, 98)

// cancellable1: (2, 77)
// cancellable2: (2, 77)

// cancellable1: (3, 12)
// cancellable2: (3, 12)

// cancellable1: (4, 28)
// cancellable2: (4, 28)

// cancellable1: (5, 22)
// cancellable2: (5, 22)

마찬가지로 한 번의 값이 전달 될 때, cancellable1cancellable2에 동시에 전달된 것을 확인할 수 있었다.

하지만 cancellable1이 처음 구독이 시작될 때, 바로 publisher에서 값이 전달되지 않도록 delay(for:scheduler)를 사용하여 구독이 모두 발생할 때, 값이 전달되도록 하였다.

profile
새롭게 알게된 것을 기록하는 공간

0개의 댓글