teunteun2
[Combine] Controlling Publishing with Connectable Publishers - developer 문서로 시작한 Combine 공부(3) 본문
[Combine] Controlling Publishing with Connectable Publishers - developer 문서로 시작한 Combine 공부(3)
teunteun2 2023. 6. 26. 22:23이전 포스팅에서 이어집니다
https://teunteun2.tistory.com/36
https://developer.apple.com/documentation/combine/controlling-publishing-with-connectable-publishers
해당 Article을 번역하고 정리하였습니다.
Controlling Publishing with Cnnectable Publishers
Publisher가 Subscriber에게 element를 보내기 시작하는 시기를 조정하는 방법
OverView
예를들어 Publisher가 자기자신의 동작에 영향을 주는 프로퍼티를 가지고 있을 때,
우리는 Publisher가 element 생성을 시작하기 전에 Publisher를 따로 설정하고 싶을텐데요
하지만 흔히 쓰이는 sink(receiveValue: ) 로 생성된 Subscriber은 무제한 element를 바로 받기 때문에
Publisher를 원하는 방식으로 설정할 수 없습니다.
또 우리가 Publisher를 준비하기 전 값을 제공해버리는 Publisher은
Subscriber를 두개 이상 가지고 있을 때에도 문제가 될 수 있습니다.
예시로 다음 시나리오를 볼까요?
- Scenario -
URLSession.DataTaskPublisher 을 생성하고 Subscriber1를 sink로 연결하면
데이터 작업이 URL의 데이터를 가져오기 시작합니다.
나중 어느시점에 똑같은 방법으로 Subscriber2를 연결했을 때,
Subscriber2가 연결되기 전에 데이터 작업이 다운로드를 완료하면
Subscriber2는 데이터를 놓치고 완료만 보게 되는거죠.
Hold Publishing by Using a Connectable Publisher
이렇게 우리가 준비되기 전에 Publisher가 element를 방출하는 것을 방지하기 위해
Combine은 ConnectablePublisher 프로토콜을 제공합니다.
ConnectablePublisher은 connect() 메서드를 호출할 때까지 element를 생성하지 않습니다.
element를 생성할 준비가 되어있고, 수요가 충족되지 않더라도
ConnectablePublisher은 connect()를 호출할 때까지 Subscriber에게 element를 전달하지 않습니다.
다음 그림은 URLSessing을 보여줍니다.
위의 DataTaskPusblisher 시나리오이지만 Subscriber 앞에 ConnectablePublisher가 있습니다.
두 Subscriber가 모두 연결될 때까지 connect()호출을 대기함으로써
데이터작업은 그때까지 다운로드를 시작하지 않습니다.
이렇게 하면 경쟁상태(race condition)가 제거되고 두 Subscriber 모두 데이터를 수신할 수 있습니다.
*RaceCondition - 경쟁상태
경쟁상태란 여러 프로세스가 공통 자원을 동시에 읽거나 쓰는 동작을 할 때,
접근 순서에 따라 결과값에 영향을 줄 수 있는 상태를 말합니다.
우리의 Combine 코드에서 ConnectablePublisher를 사용하기 위해선,
makeConnectable() operator를 사용해서
기존 Publisher를 Publishers.MakeConntectable 인스턴스로 감싸야합니다.
아래의 코드는 어떻게 makeConnectable()가
DataTaskPusblisher의 경쟁상태(race condition)를 해결하는지 보여줍니다.
cancellable1로 식별되어있는 sink를 연결하면, 작업이 시작됩니다.
이 시나리오에서는 cancellable2로 식별되어있는 두번째 sink가 1초 후까지 연결되지 않으며
두번째 sink가 연결되지 전에 DataTaskPublisher가 완료될 수 있습니다.
대신, ConnectablePublsiher를 명시적으로 사용하면, 앱이 connect()를 호출한 후에만
데이터작업이 시작되며, 이는 2초 지연 후에 수행됩니다.
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()
}
***중요
connect()는 유지해야하는 Cancellable 인스턴스를 리턴합니다.
이 인스턴스를 사용해서 Publishing을 취소하려면 명시적으로 cancel()을 호출하거나 deinitialize를 허용해야 합니다.
Use the AutoConnect Operator If You Don't Need to Explicitly Connect
Publishers.Multicast와 Timer.TimerPublisher와 같이 일부 Combine publisher은
이미 ConnectablePublisher를 구현합니다.
이 Publisher를 사용하면 반대의 문제가 발생할 수 있습니다.
Publisher를 구성하거나 여러 Subscriber에 연결할 필요가 없을 때에도
명시적으로 connect()를 호출해야하는 것이 부담이 될 수 있습니다.
이러한 경우, ConnectablePublisher은 autoconnect() 연산자를 제공합니다.
이 연산자는 Subscriber가 subscribe(_:) 메서드로 Publisher에 연결되면 즉시 connect()를 호출합니다.
다음 예시에서는 autoconnect()를 사용하므로
Subscriber은 즉시 1초당 1회 발생하는 Timer.TimerPublisher에서 element를 수신합니다.
autoconnect()를 사용하지 않으면 이 예시에서는 특정 시점에서 connect()를 호출해서
타이머 Publisher를 명시적으로 시작해야합니다.
let cancellable = Timer.publish(every: 1, on: .main, in: .default)
.autoconnect()
.sink() { date in
print ("Date now: \(date)")
}
해당 아티클에서 나온 Publisher들
**URLSession.DataTaskPublisher
A publisher that delivers the results of performing URL session data tasks.
struct DataTaskPublisher
** Publishers.Multicast
A publisher that uses a subject to deliver elements to multiple subscribers.
final class Multicast<Upstream, SubjectType> where
Upstream : Publisher,
SubjectType : Subject,
Upstream.Failure == SubjectType.Failure,
Upstream.Output == SubjectType.Output
**Timer.TimerPublisher
A publisher that repeatedly emits the current date on a given interval.
final class TimerPublisher
'iOS' 카테고리의 다른 글
WWDC23 - Explore SwiftUI Animation (0) | 2023.08.03 |
---|---|
[Combine] Processing Published Elements with Subscribers - developer 문서로 시작한 Combine 공부(4) (0) | 2023.06.27 |
[Combine] Receiving and Handling Events with Combine - developer 문서로 시작한 Combine 공부(2) (0) | 2023.06.23 |
[Combine] 정의 및 구조 - developer 문서로 시작한 Combine 공부(1) (0) | 2023.06.22 |
WWDC23 - Add SharePlay to your app (0) | 2023.06.07 |