【The Composable Architecture (TCA) 源码解析】03 - Effect 扩展之 Animation

159 阅读1分钟

此源码解析适用于 0.41.0 之前的版本。最新的源码已经把 Reducer 重构成了一个协议 ReducerProtocol,但核心的实现方法还是离不开这个版本的源码。

这个扩展的作用是将 Effect 副作用导致的 UI 变化以指定的动画展现。

extension Effect {
  // 用指定的动画执行 Effect
  public func animation(_ animation: Animation? = .default) -> Self {
    switch self.operation {
    case .none:
      return .none
    case let .publisher(publisher):
		// 把 `publisher` 包装在 `AnimatedPublisher` 中,
		// 使 `publisher` 在指定的动画下发出每一个值。
		// 下面有对 `AnimatedPublisher ` 的仔细讲解。
      return Self(
        operation: .publisher(
          AnimatedPublisher(upstream: publisher, animation: animation).eraseToAnyPublisher()
        )
      )
    case let .run(priority, operation):
		// 返回一个新的 `run`,使用 `withAnimation` 包装 `send(value)`
      return Self(
        operation: .run(priority) { send in
          await operation(
            Send { value in
              withAnimation(animation) {
                send(value)
              }
            }
          )
        }
      )
    }
  }
}

// `AnimatedPublisher` 实现 `Publisher` 协议,并且其上游 `Upstream` 是一个 `Publisher`
private struct AnimatedPublisher<Upstream: Publisher>: Publisher {
	// 输出和错误类型与上游相同
  typealias Output = Upstream.Output
  typealias Failure = Upstream.Failure

  // 保存上游和动画类型
  var upstream: Upstream
  var animation: Animation?

	// `Publisher` 协议的方法,收到订阅者
  func receive<S: Combine.Subscriber>(subscriber: S)
  where S.Input == Output, S.Failure == Failure {
	  // 自定义的订阅者类型
    let conduit = Subscriber(downstream: subscriber, animation: self.animation)
	  // 将订阅者转发给上游 `Publisher`
    self.upstream.receive(subscriber: conduit)
  }

  // 自定义的订阅者类型
  private class Subscriber<Downstream: Combine.Subscriber>: Combine.Subscriber {
	  // 输入和错误类型与下游 `Subscriber` 相同
    typealias Input = Downstream.Input
    typealias Failure = Downstream.Failure

    // 保存上游和动画类型
    let downstream: Downstream
    let animation: Animation?

    init(downstream: Downstream, animation: Animation?) {
      self.downstream = downstream
      self.animation = animation
    }

    func receive(subscription: Subscription) {
		// 收到订阅,转发给下游
      self.downstream.receive(subscription: subscription)
    }

    func receive(_ input: Input) -> Subscribers.Demand {
		// 收到输入,转发给下游。
      // `AnimatedPublisher` 最核心的就是用 `withAnimation` 包装转发输入的操作,
		// 使得下游以动画的形式收到输入。
      withAnimation(self.animation) {
        self.downstream.receive(input)
      }
    }

    func receive(completion: Subscribers.Completion<Failure>) {
		// 收到 completion,转发给下游
      self.downstream.receive(completion: completion)
    }
  }
}