ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TCA - concatenate & merge (여러 Effect를 단일 Effect로 만들기)
    TCA 2022. 10. 25. 14:54

    안녕하세요. 그린입니다🍏

    이번 포스팅에서는 TCA의 Effect편입니다.

    Effect를 병합하는 대표적인 두개의 방법 concatenate와 merge에 대해 알아보겠습니다🙌

     

    예전에 제가 포스팅으로 Rx를 학습하면서 concat과 merge에 대해 소개하면서 차이점을 알아본적이 있습니다.

    TCA에서 Effect라고 해서 다를게 없어요.

    Observable, Publisher랑 동일하게 작동합니다 원리는!

    그렇기에 만약 TCA에서 낯선것처럼 느껴지시면 아래 포스팅을 보고 오시는것도 좋아요🙏🏻

    https://green1229.tistory.com/166

     

    concat & merge

    안녕하세요. 그린입니다🟢 이번 포스팅에서는 concat과 merge에 대해 학습해보겠습니다🧑🏻‍💻 일단 두 단어의 사전적인 의미부터 살펴보면, concat은 concatenate의 약자로 연줄의, 사슬을 잇다 라

    green1229.tistory.com

     

    그럼 TCA에서의 concatenate와 merge 순서로 시작합니다~🥳

     

    concatenate의 정의 및 구현

    extension EffectPublisher {
      @inlinable
      public static func concatenate(_ effects: Self...) -> Self {
        Self.concatenate(effects)
      }
    
      @inlinable
      public static func concatenate<C: Collection>(_ effects: C) -> Self where C.Element == Self {
        effects.reduce(.none) { $0.concatenate(with: $1) }
      }
      
      @inlinable
      @_disfavoredOverload
      public func concatenate(with other: Self) -> Self {
        switch (self.operation, other.operation) {
        case (_, .none):
          return self
        case (.none, _):
          return other
        case (.publisher, .publisher), (.run, .publisher), (.publisher, .run):
          return Self(
            operation: .publisher(
              Publishers.Concatenate(prefix: self, suffix: other).eraseToAnyPublisher()
            )
          )
        case let (.run(lhsPriority, lhsOperation), .run(rhsPriority, rhsOperation)):
          return Self(
            operation: .run { send in
              if let lhsPriority = lhsPriority {
                await Task(priority: lhsPriority) { await lhsOperation(send) }.cancellableValue
              } else {
                await lhsOperation(send)
              }
              if let rhsPriority = rhsPriority {
                await Task(priority: rhsPriority) { await rhsOperation(send) }.cancellableValue
              } else {
                await rhsOperation(send)
              }
            }
          )
        }
      }
    }

    concatenate는 여러 Effect를 단일 Effect로 연결해 실행해주는 기능을 합니다.

    다만 여기서 중요한건 여러 Effect들을 병합해 하나의 Effect로 방출할때 순서대로 실행시키면서 합친다는 점입니다.

    먼저 들어온 Effect를 실행해 완료 혹은 취소 된 후 이후 Effect를 실행합니다.

     

    그걸 염두해두고 더 보시죠!

    정의가 비교적 어렵진 않습니다.

     

    우선 내부 메서드인 concatenate를 알아보겠습니다.

    Self타입은 당연히 Effect일것이고 해당 Effect를 받아 순서를 체크합니다.

    먼저 들어온 Effect가 아니라면 대기하고 순서에 맞게 처리하는 구성입니다.

     

    이 내부 메서드를 가지고 여러 Effect들을 실제적으로 받아 reduce 함수로 병합하는 과정을 거치면서 내부에서는 해당 메서드를 호출하여 실행해줍니다.

     

    결론적으로는 아래와 같이 사용됩니다.

    return Effect.concatenate([
      Effect(value: .firstAction),
      Effect(value: .secondAction),
    ])

    즉 firstAction을 먼저 실행시키고 끝난 후 순차적으로 secondAction을 실행시킵니다.

    만약 아래같이 사용하면 순서가 달라집니다.

    return Effect.concatenate([
      Effect(value: .secondAction),
      Effect(value: .firstAction),
    ])

    이럴 경우 secondAction을 실행 후 firstAction을 실행시키죠.

    list에 넣어주는 순서를 꼭 유의해야 합니다.

     

    주로 concatenate는 순서를 보장해야하는 구현들에 사용합니다.

    Action이 실행되면 어떤 effect를 취소하고 그 다음 effect를 실행해야 한다거나,

    토스트 메시지를 먼저 띄우고 뒤로 뷰를 이동시켜야 한다거나 할때 주로 사용합니다.

     

    아주 쉽죠!? 기억할건 하나에요! 순.서.대.로🙌

     

    그럼 이어서 merge에 대해 알아보죠!

     

    merge의 정의 및 구현

    extension EffectPublisher {
      @inlinable
      public static func merge(_ effects: Self...) -> Self {
        Self.merge(effects)
      }
      
      @inlinable
      public static func merge<S: Sequence>(_ effects: S) -> Self where S.Element == Self {
        effects.reduce(.none) { $0.merge(with: $1) }
      }
    
      @inlinable
      public func merge(with other: Self) -> Self {
        switch (self.operation, other.operation) {
        case (_, .none):
          return self
        case (.none, _):
          return other
        case (.publisher, .publisher), (.run, .publisher), (.publisher, .run):
          return Self(operation: .publisher(Publishers.Merge(self, other).eraseToAnyPublisher()))
        case let (.run(lhsPriority, lhsOperation), .run(rhsPriority, rhsOperation)):
          return Self(
            operation: .run { send in
              await withTaskGroup(of: Void.self) { group in
                group.addTask(priority: lhsPriority) {
                  await lhsOperation(send)
                }
                group.addTask(priority: rhsPriority) {
                  await rhsOperation(send)
                }
              }
            }
          )
        }
      }
    }

    merge는 concatenate와 다르게 순서를 타지 않습니다.

    여러 Effect를 단일 Effect로 동시에 병합해버립니다.

    그럼으로 순차적으로 list에 넣어줬어도 동시에 실행하기에 순서를 Effect간 순서를 보장할 수는 없습니다!

     

    내부 merge 메서드 구현을 보시죠.

    concatenate와 거진 폼은 유사해요.

    다만 concatenate는 lhs, rhs로 순서를 구분했다하면 merge는 비동기로 들어온 Effect에 대해 바로 처리해버립니다.

     

    사용법은 아래와 같아요.

    return Effect.merge([
      Effect(value: .firstAction),
      Effect(value: .secondAction),
    ])

    순서가 보장되지 않으니 first와 second 어떤것이 먼저 실행되고 끝날지는 모릅니다.

     

    주로 관련 없는 각각의 State들을 변경할때 순서의 보장이나 선후 관계가 필요 없을때 저는 주로 사용합니다.

     

    마무리

    concatenate와 merge 사용 진짜 많이 이뤄지니까 알면 좋습니다!

    아니 알아야되는것중 하나죠..!

    물론 concatenate와 merge를 한 Action Switch문에서 중첩해서 단일 Effect로도 만들어 사용할 수 있어요.

    자유롭게 순서의 영향을 받는것인지 판단하고 사용하면 좋습니다🕺🏻

     

    [참고 자료]

    https://github.com/pointfreeco/swift-composable-architecture/blob/main/Sources/ComposableArchitecture/Effect.swift#L367

     

    GitHub - pointfreeco/swift-composable-architecture: A library for building applications in a consistent and understandable way,

    A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. - GitHub - pointfreeco/swift-composable-architecture: A library for bu...

    github.com

    'TCA' 카테고리의 다른 글

    TCA - Testing Effects (feat. unimplemented)  (0) 2022.10.31
    TCA - fireAndForget  (0) 2022.10.27
    TCA - Throttling  (2) 2022.10.20
    TCA - Debouncing  (0) 2022.10.17
    TCA - Timer  (2) 2022.10.13
Designed by Tistory.