iOS/Combine

[Combine] Combining Elements from Multiple Publishers 3 - Operator 공부 10

Dev_Pingu 2022. 5. 12. 01:39
반응형

안녕하세요 Pingu입니다.🐧

 

지난 글에서는 Combine의 Combining Elements from Multiple Publishers로 분류된 것들 중 Republishing Elements from Multiple Publishers as an Interleaved Stream 역할을 하는 Merge 시리즈에 대해 알아봤습니다. 여러 개의 Publisher를 하나의 Publisher처럼 사용할 수 있게 해 줬죠.

 

이번 글에서는 드디어 Combining Elements from Multiple Publishers 시리즈의 마지막인 Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers 역할을 하는 Zip 시리즈에 대해 알아보도록 하겠습니다.

 

Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers

이번에 공부할 Operator들을 분류한 이름을 보면 여러 개의 Publisher에서 가장 오래 사용되지 않은 값들을 모아서 처리하는 역할을 한다고 되어있네요.

 

일단 어떤 Publisher들이 있는지부터 보겠습니다.

  • Zip
  • Zip3
  • Zip4

음 여기서도 뒤에 숫자가 늘어나는 거로 봐선 동일한 동작을 하지만 처리하는 Publisher의 개수만 달라지나 봅니다.

 

이 Zip Publisher들을 활용해서 만든 Operator는 아래와 같습니다.

  • zip(_ other:)
  • zip(_ other: _ transform:)
  • zip(_ publisher1, _ publisher2)
  • zip(_ publisher1, _ publisher2, _ transform:)
  • zip(_ publisher1, _ publisher2, _ publisher3)
  • zip(_ publisher1, _ publisher2, _ publisher3, _ transform:)

Opertaor도 보면 매개변수로 Publisher만 있는 것, transform이라는 클로저도 필요한 것으로 크게 두 개로 나뉩니다. 각각은 처리하는 Publisher의 개수만 달라지는 것이므로 이번 글에서는 2개의 Publisher를 처리하는 Zip Publisher와 이를 활용해서 만든 Operator에 대해서만 알아보도록 하겠습니다.

 

Zip

이번에 알아볼 Publisher는 Zip입니다.

정의를 보니 2개의 Upstream Publisher를 zip 해서 만들어진 Publisher라고 합니다.

zip(_ other:)

Zip Publisher를 활용해서 만든 첫 번째 Operator는 zip(_ other:)입니다. 정의를 보면 2개의 Publisher의 값을 튜플로 만들어서 Downstream으로 전달한다고 합니다.

 

튜플로 만든다고 하니까 예전에 알아본 CombineLatest에서도 비슷한 걸 본 거 같네요.

한 번 사용해보고 차이점을 살펴봐야겠네요.

let firstPublisher = PassthroughSubject<Int, Never>()
let secondPublisher = PassthroughSubject<Int, Never>()

firstPublisher
    .zip(secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(1)
secondPublisher.send(11)

firstPublisher.send(2)
secondPublisher.send(12)

firstPublisher.send(3)
firstPublisher.send(4)

// zip(_ other:) 예제 코드
(1, 11)
(2, 12)

위 코드를 보면 얼핏 보면 CombineLatest와 별 차이가 없어 보이는데요, 결과를 보면 차이를 알 수 있습니다.

위 코드에서 firstPublisher가 3, 4를 내보냈을 땐 Downstream으로 전달된 것이 없는데요, 이는 secondPublisher가 내보낸 값은 이미 모두 처리했기 때문입니다.

firstPublisher가 값을 4개 보냈더라도 secondPublisher가 값을 2개만 보냈기 때문에 downstream으로 전달된 튜플은 2개뿐인 것이죠.

 

CombineLatest는 처리하는 Publisher들에게 하나 이상의 값을 받은 이후에는 어떤 Publisher에게 값을 받아도 모든 Publisher의 가장 최신 값을 가지고 처리했었습니다. 즉 위 코드에서 zip이 아닌 combineLatest를 사용했다면 (3, 12), (4, 12)도 downstream으로 전달됐을 거예요.

 

Zip의 작동방식을 쉽게 이해하려면 배열과 index 개념을 생각하면 됩니다. 각각의 Publisher들에게 받은 값은 순서대로 각각 배열에 쌓이고 동일한 index의 값이 모두 존재할 때 Downstream으로 튜플을 전달한다고 생각하면 이해가 잘 됩니다.(저는 그랬어요 😄)

 

즉 방금 코드에서 3, 4를 Downstream으로 보내기 위해서는 아래와 같이 secondPublisher도 값을 2개 더 보내주면 됩니다.

let firstPublisher = PassthroughSubject<Int, Never>()
let secondPublisher = PassthroughSubject<Int, Never>()

firstPublisher
    .zip(secondPublisher)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send(1)
secondPublisher.send(11)

firstPublisher.send(2)
secondPublisher.send(12)

firstPublisher.send(3)
firstPublisher.send(4)

secondPublisher.send(13)
firstPublisher.send(completion: .finished) // firstPublisher가 내보낸 4는 아직 남아있음.
secondPublisher.send(14) // 모든 값이 처리되어 finished 됨

// zip(_ other:) 예제 코드
(1, 11)
(2, 12)
(3, 13)
(4, 14)
finished

completion의 경우 처리하는 모든 Publisher가 finished를 보내면 바로 끝나지만, 위와 같이 하나만 finished를 보낸 경우엔 처리하는 모든 Publisher에게 받은 값이 처리된 이후에 finished가 출력되는 걸 볼 수 있습니다.

 

즉 Publisher finished를 전달하더라도 해당 Publisher가 이미 내보낸 값들은 모두 Downstream으로 전달될 수 있습니다.

zip(_ other:, _ transform:)

그럼 다음으론 zip(_ other:, _ transform:)을 알아봅시다. 아까 알아본 zip(_ other:)은 튜플만 downstream으로 보낼 수 있었는데요, zip(_other:, _transform:)은 transform에 전달하는 클로저를 활용해서 다른 타입도 전달할 수 있게 해 줍니다.

 

바로 사용해볼게요.

let firstPublisher = PassthroughSubject<String, Never>()
let secondPublisher = PassthroughSubject<String, Never>()

firstPublisher
    .zip(secondPublisher) { value1, value2 in
        return value1 + " " + value2
    }
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0) })

firstPublisher.send("I'm First")
secondPublisher.send("I'm Second")

firstPublisher.send("I'm First!")
firstPublisher.send("I'm First!!")

secondPublisher.send("I'm Second!")

firstPublisher.send(completion: .finished) // firstPublisher가 내보낸 "I'm First!!"는 아직 남아있음

secondPublisher.send("Bye~") // 모든 값이 처리되어 finished 됨

// zip(_ other:, _ transform:) 예제 코드
I'm First I'm Second
I'm First! I'm Second!
I'm First!! Bye~
finished

위 코드와 같이 Publisher에게 받은 값을 원하는 형태로 Downstream에 전달할 수 있습니다.

또한 completion을 처리하는 방법도 동일합니다.

 

Zip3, Zip4의 경우 Zip과 처리하는 Publisher의 개수만 다르고 모두 동일하게 동작합니다.

 

이렇게 Combining Elements from Multiple Publishers로 분류된 것들 중 Collecting and Republishing the Oldest Unconsumed Elements from Multiple Publishers 역할을 하는 Zip 시리즈를 알아봤습니다.

 

다음 글에서는 Republishing Elements by Subscribing to New Publishers로 분류된 것들에 대해 알아보도록 하겠습니다.

 

전체 코드는 여기에서 볼 수 있습니다.

 

감사합니다~!

반응형