본문 바로가기

Combine

[Combine] Subject 알아보기 (PassthroughSubject / CurrentValueSubject)

이번 글에선 Subject 에 대해 알아보도록 하겠습니다.

공식 문서에도 보이듯 Subject 는

Publisher 프로토콜을 따르는 것을 확인할 수 있습니다.

 

설명을 살펴보면

Subject 는 스트림에 send(_:) 메서드를 호출해서

값을 주입(inject) 할 수 있는 publisher 라고 작성되어 있습니다.

 

Subject 내부는 어떻게 만들어져 있는지 살펴보면

 

 

3가지의 send 메서드가 있는것을 확인할 수 있습니다.

중요한건 value, completion, subscription 을

subscriber 에게 전달하는 것을 확인할 수 있습니다.

 

그렇다면 subject 라는 것도 프로토콜이니

누군가 채택을 해서 사용할텐데

이를 채택해서 사용하는 것은

CurrentValueSubject, PassthroughSubect 

라는 것이 있습니다.

순서대로 같이 살펴보도록 하겠습니다.

 

1. CurrentValueSubject

 

CurrentValueSubject 는 현재 값을 가지며, 값을 업데이트하면

현재 값을 subscriber 에게 즉시 전달하는 특징을 갖고 있습니다.

 

위의 메서드와 같이 CurrentValueSubject 를 사용할땐

반드시 value 를 설정해줘야 합니다.

 

CurrentValueSubject 예제를 살펴보면

 

// CurrentValueSubject 예제
let currentValueSubject = CurrentValueSubject<String, Never>("Initial Value")

let currentValueSubscriber1 = currentValueSubject.sink { value in
    print("Subscriber 1 received value: \(value)")
}

currentValueSubject.send("🍁")

let currentValueSubscriber2 = currentValueSubject.sink { value in
    print("Subscriber 2 received value: \(value)")
}

currentValueSubject.send("🌕")

 

value 를 "Initial Value" 로 설정해주고 subscriber1, subscriber2 를 두고

send 하는 코드를 작성해봤습니다.

 

 

subscriber1 이 처음 호출될때 Initial Value 가 제일 먼저

호출되는 것을 확인할 수 있고 

마지막에 🌕 을 send 할때 subscriber1, subscriber2

두개의 값이 모두 업데이트 된 것을 확인할 수 있습니다.

(추가적으로 호출의 순서가 보장되지 않는다는 것 또한 확인할 수 있습니다.)

 

2. PassthroughSubject

 

 

currentValueSubject 와는 다르게

init 메서드 사용시 파라미터가 필요하지 않은걸 확인할 수 있습니다.

 

코드를 통해 살펴보면

 

// PassthroughSubject 예제
let passthroughSubject = PassthroughSubject<String, Never>()

let passthroughSubscriber1 = passthroughSubject.sink { value in
    print("Subscriber 1 received value: \(value)")
}

passthroughSubject.send("🍡")
passthroughSubject.send("🌾")

let passthroughSubscriber2 = passthroughSubject.sink { value in
    print("Subscriber 2 received value: \(value)")
}

passthroughSubject.send("🥮")

 

 

PassthroughSubject 또한

마지막에 🥮 을 send 할때 subscriber1, subscriber2

두개의 값이 모두 업데이트 된 것을 확인할 수 있습니다.

 

 

그렇다면 지금까지 알아본 것을 어떻게 활용할 수 있을지

알아보도록 하겠습니다.

 

// Product 모델 정의
struct Product {
    let name: String
}

// 초기 제품 목록
let initialProducts = [
    Product(name: "iPhone14"),
    Product(name: "AirPods2"),
    Product(name: "MacBookPro"),
    Product(name: "MacBookAir")
]

// CurrentValueSubject를 사용하여 제품 목록을 저장하고 공유합니다.
var productSubject = CurrentValueSubject<[Product], Never>(initialProducts)

// 현재 제품 목록을 출력
print("🍎 현재 Apple 제품 목록")
print(productSubject.value.map { $0.name })

// 새로운 제품을 추가하여 제품 목록을 업데이트합니다.
let newProduct = Product(name: "iPhone15ProMax")
productSubject.send(productSubject.value + [newProduct])

// 업데이트된 제품 목록을 출력
print("🍏 업데이트된 제품 목록")
print(productSubject.value.map { $0.name })

 

초기 제품 목록이 있기에 Subject 를 만들어줄때

CurrentValueSubject 를 사용했고,

제품의 이름에 접근하기 위해 value 를 사용했습니다.

Subject 에 의해 래핑된 value 는 변경될 때마다 새 요소로 게시된다고 작성되어 있습니다.

 

value 로만 접근하게 된다면

 

 

이런 결과값이 출력되기 때문에 map 연산자를 사용하여

아래와 같이 name 만 가져올 수 있게 하였습니다.

 

이렇게 현제 제품 목록을 확인할 수 있고

 

 

productSubject 에 newProduct 를 추가하여 send 하게 된다면

 

 

 

prodcutSubject 값이 업데이트 되어 출력된 것을 확인할 수 있습니다!

 

 

다음 글에선 작업 중인 항목을 취소할 수 있는

Cancellable 에 대해 알아보도록 하겠습니다.

읽어주셔서 감사합니다! 😊