【The Composable Architecture (TCA) 源码解析】09 - ViewStore

316 阅读2分钟

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

ViewStore 可以监听 state 的变化,并且发送 action。通常用于试图,例如 SwiftUI views,UIViewUIViewController

在 SwiftUI 应用中,ViewStore 通常可以在 WithViewStore 中访问。例如:

var body: some View {
  WithViewStore(self.store, observe: { $0 }) { viewStore in
    VStack {
      Text(“Current count: \(viewStore.count)”)
      Button(“Increment”) { viewStore.send(.incrementButtonTapped) }
    }
  }
}

ViewStore 还可以被 @ObservedObject 标记:

@ObservedObject var viewStore: ViewStore<State, Action>

在 UIKit 中,ViewStore 可以使用 Store 来创建,然后订阅 state 的变化:

let store: Store<State, Action>
let viewStore: ViewStore<State, Action>
private var cancellables: Set<AnyCancellable> = []

init(store: Store<State, Action>) {
  self.store = store
  self.viewStore = ViewStore(store)
}

func viewDidLoad() {
  super.viewDidLoad()

  self.viewStore.publisher.count
    .sink { [weak self] in self?.countLabel.text = $0 }
    .store(in: &self.cancellables)
}

@objc func incrementButtonTapped() {
  self.viewStore.send(.incrementButtonTapped)
}

注意ViewStore 类不是线程安全的,与它(以及从 Store 派生自的)的所有交互必须发生在同一线程上。对于 SwiftUI 应用,所有交互都必须发生在主线程上。

@dynamicMemberLookup
public final class ViewStore<State, Action>: ObservableObject {
  // 为了支持 iOS 13,而不是用 `@Published`,显式地声明 `objectWillChange`
  public private(set) lazy var objectWillChange = ObservableObjectPublisher()

  // 发送 action 的闭包
  private let _send: (Action) -> Task<Void, Never>?
  // 当前的 state
  fileprivate let _state: CurrentValueRelay<State>
  // 保存对 `store.state` 的订阅
  private var viewCancellable: AnyCancellable?

  public init(
    _ store: Store<State, Action>,
    removeDuplicates isDuplicate: @escaping (State, State) -> Bool
  ) {
    // 直接调用 `store` 的 `send` 方法
    self._send = { store.send($0) }
    // 初始化 state
    self._state = CurrentValueRelay(store.state.value)
    // 订阅 `store.state` 的变化,并更新自己的 `_state`
    self.viewCancellable = store.state
      .removeDuplicates(by: isDuplicate)
      .sink { [weak objectWillChange = self.objectWillChange, weak _state = self._state] in
        guard let objectWillChange = objectWillChange, let _state = _state else { return }
        objectWillChange.send()
        _state.value = $0
      }
  }

  internal init(_ viewStore: ViewStore<State, Action>) {
    self._send = viewStore._send
    self._state = viewStore._state
    self.objectWillChange = viewStore.objectWillChange
    self.viewCancellable = viewStore.viewCancellable
  }

  // 一个会发出 `State` 的 Publisher。支持动态成员查找(dynamic member lookup)。例如:
  //
  // ```swift
  // viewStore.publisher.alert
  //   .sink { ... }
  // ```
  //
  // 当这个 Publisher 发出值时,`ViewStore` 的 state 已经更新,所以下面的判断是可以通过的:
  //
  // ```swift
  // viewStore.publisher
  //   .sink { precondition($0 == viewStore.state) }
  // ```
  //
  // 这意味着可以使用闭包中的 state 或者直接使用 `viewStore.state`。
  //
  // 要注意的是,在一个 Publisher 上调用 `.sink` 的顺序并不意味着是闭包执行的顺序。
  // 所以每个闭包执行的代码必须是相互独立的。
  public var publisher: StorePublisher<State> {
    // 具体实现请查看 `StorePublisher`
    StorePublisher(viewStore: self)
  }

  // 当前的 state
  public var state: State {
    self._state.value
  }

  // 根据传入的 key path 返回对应的 state
  public subscript<Value>(dynamicMember keyPath: KeyPath<State, Value>) -> Value {
    self._state.value[keyPath: keyPath]
  }

  // 发送 action 到 store。
  // 返回值是 `ViewStoreTask`,可以用它来控制 Effect 的生命周期。例如 SwiftUI 的 `task` modifier:
  // ```swift
  // .task { await viewStore.send(.task).finish() }
  // ```
  @discardableResult
  public func send(_ action: Action) -> ViewStoreTask {
    .init(rawValue: self._send(action))
  }

  // 以指定的动画发送 action 到 store
  @discardableResult
  public func send(_ action: Action, animation: Animation?) -> ViewStoreTask {
    withAnimation(animation) {
      self.send(action)
    }
  }

/// 发送 action 到 store,然后在某个 state 为 `true` 时挂起。

/// 此方法可用于与 async/await 代码交互,可以在 Effect 执行某些工作期间挂起。
/// 一个常见的例子是 SwiftUI 的 `.refreshable` 方法,它在执行工作时在屏幕上显示加载指示器。
///
/// 例如,列表的下拉刷新,我们可以这样写:
  ///
  /// ```swift
  /// struct State: Equatable {
  ///   var isLoading = false
  ///   var response: String?
  /// }
  ///
  /// enum Action {
  ///   case pulledToRefresh
  ///   case receivedResponse(TaskResult<String>)
  /// }
  ///
  /// struct Environment {
  ///   var fetch: () async throws -> String
  /// }
  ///
  /// let reducer = Reducer<State, Action, Environment> { state, action, environment in
  ///   switch action {
  ///   case .pulledToRefresh:
  ///     state.isLoading = true
  ///     return .task {
  ///       await .receivedResponse(TaskResult { try await environment.fetch() })
  ///     }
  ///
  ///   case let .receivedResponse(result):
  ///     state.isLoading = false
  ///     state.response = try? result.value
  ///     return .none
  ///   }
  /// }
  /// ```
  ///
  /// 使用了 `isLoading` 来表示请求是否正在执行。 View 如下:
  ///
  /// ```swift
  /// struct MyView: View {
  ///   let store: Store<State, Action>
  ///
  ///   var body: some View {
  ///     WithViewStore(self.store, observe: { $0 }) { viewStore in
  ///       List {
  ///         if let response = viewStore.response {
  ///           Text(response)
  ///         }
  ///       }
  ///       .refreshable {
  ///         await viewStore.send(.pulledToRefresh, while: \.isLoading)
  ///       }
  ///     }
  ///   }
  /// }
  /// ```
  ///
  /// 这里我们使用了 `send(_:while:)`,当 `isLoading` 为 `true` 时挂起。
	/// 一旦它变成 `false` 将会继续执行, 会告诉 `.refreshable` 工作已经完成,加载指示器消失。
  @MainActor
  public func send(_ action: Action, while predicate: @escaping (State) -> Bool) async {
    let task = self.send(action)
    await withTaskCancellationHandler {
      task.rawValue?.cancel()
    } operation: {
      // 具体实现请查看此方法
      await self.yield(while: predicate)
    }
  }

  /// 与上一个方法类似,不同的是此方法可以以指定动画执行 action
  @MainActor
  public func send(
    _ action: Action,
    animation: Animation?,
    while predicate: @escaping (State) -> Bool
  ) async {
    let task = withAnimation(animation) { self.send(action) }
    await withTaskCancellationHandler {
      task.rawValue?.cancel()
    } operation: {
      await self.yield(while: predicate)
    }
  }

  /// 当 `predicate ` 为 `true` 时挂起当前任务
  @MainActor
  public func yield(while predicate: @escaping (State) -> Bool) async {
    // 实现思路:订阅 `self.publisher`,等待它发出第一个值。
    // 如果 `predicate` 为 true,值就不会发出,代码会停在这里
    if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
      // 使用 await 等待 `self.publisher` 发出第一个值
      _ = await self.publisher
        .values
        .first(where: { !predicate($0) })
    } else {
      let cancellable = Box<AnyCancellable?>(wrappedValue: nil)
      try? await withTaskCancellationHandler {
        cancellable.wrappedValue?.cancel()
      } operation: {
        // 检查 task 是否已经取消
        try Task.checkCancellation()
        // 使用 await 等待 continuation 完成
        try await withUnsafeThrowingContinuation {
          (continuation: UnsafeContinuation<Void, Error>) in
          guard !Task.isCancelled else {
            continuation.resume(throwing: CancellationError())
            return
          }
          cancellable.wrappedValue = self.publisher
            .filter { !predicate($0) }
            .prefix(1)
            .sink { _ in
              // 得到了第一个值,continuation 继续
              continuation.resume() 
              // 这里目的是防止 cancellable 被提前释放
              _ = cancellable
            }
        }
      }
    }
  }

  /// 从 Store 派生一个 `Binding`,它可以防止直接对 State 进行修改,而是发送 Action 给 Store。
	/// 非常适合用于 SwiftUI 的支持双向绑定的组件。例如 `TextField`:
  ///
  /// ```swift
  /// struct State { var name = "" }
  /// enum Action { case nameChanged(String) }
  ///
  /// TextField(
  ///   "Enter name",
  ///   text: viewStore.binding(
  ///     get: { $0.name },
  ///     send: { Action.nameChanged($0) }
  ///   )
  /// )
  /// ```
  public func binding<Value>(
    get: @escaping (State) -> Value,
    send valueToAction: @escaping (Value) -> Action
  ) -> Binding<Value> {
    ObservedObject(wrappedValue: self)
      .projectedValue[get: .init(rawValue: get), send: .init(rawValue: valueToAction)]
  }

  /// `binding` 方法的一个变体。
  /// alert binding 的例子:
  ///
  /// ```swift
  /// struct State { var alert: String? }
  /// enum Action { case alertDismissed }
  ///
  /// .alert(
  ///   item: self.store.binding(
  ///     get: { $0.alert },
  ///     send: .alertDismissed
  ///   )
  /// ) { alert in Alert(title: Text(alert.message)) }
  /// ```
  public func binding<Value>(
    get: @escaping (State) -> Value,
    send action: Action
  ) -> Binding<Value> {
    self.binding(get: get, send: { _ in action })
  }

  /// `binding` 方法的一个变体。
  /// `TextField` 的例子:
  ///
  /// ```swift
  /// typealias State = String
  /// enum Action { case nameChanged(String) }
  ///
  /// TextField(
  ///   "Enter name",
  ///   text: viewStore.binding(
  ///     send: { Action.nameChanged($0) }
  ///   )
  /// )
  /// ```
  public func binding(
    send valueToAction: @escaping (State) -> Action
  ) -> Binding<State> {
    self.binding(get: { $0 }, send: valueToAction)
  }

  /// `binding` 方法的一个变体。
  /// alert binding 的例子:
  ///
  /// ```swift
  /// typealias State = String
  /// enum Action { case alertDismissed }
  ///
  /// .alert(
  ///   item: viewStore.binding(
  ///     send: .alertDismissed
  ///   )
  /// ) { title in Alert(title: Text(title)) }
  /// ```
  public func binding(send action: Action) -> Binding<State> {
    self.binding(send: { _ in action })
  }

  private subscript<Value>(
    get state: HashableWrapper<(State) -> Value>,
    send action: HashableWrapper<(Value) -> Action>
  ) -> Value {
    get { state.rawValue(self.state) }
    set { self.send(action.rawValue(newValue)) }
  }
}

extension ViewStore where State: Equatable {
  public convenience init(_ store: Store<State, Action>) {
    self.init(store, removeDuplicates: ==)
  }
}

extension ViewStore where State == Void {
  public convenience init(_ store: Store<Void, Action>) {
    self.init(store, removeDuplicates: ==)
  }
}

/// `send` 方法返回的类型,代表着一个 `Effect` 的生命周期。
// 可以使用此值将 `Effect` 的生命周期关联到异步上下文中,如 `task` modifier:
///
/// ```swift
/// .task { await viewStore.send(.task).finish() }
/// ```
public struct ViewStoreTask: Hashable, Sendable {
  fileprivate let rawValue: Task<Void, Never>?

  // 取消底层的 task,并且等待它完成
  public func cancel() async {
    self.rawValue?.cancel()
    await self.finish()
  }

  // 等待 task 完成
  public func finish() async {
    await self.rawValue?.cancellableValue
  }

  /// task 是否已取消。一旦变成 `true`,就无法再重新执行
  public var isCancelled: Bool {
    self.rawValue?.isCancelled ?? true
  }
}

// State 的 Publisher
@dynamicMemberLookup
public struct StorePublisher<State>: Publisher {
  public typealias Output = State
  public typealias Failure = Never

  public let upstream: AnyPublisher<State, Never>
  public let viewStore: Any

  fileprivate init<Action>(viewStore: ViewStore<State, Action>) {
    self.viewStore = viewStore
    // 上游就是 state
    self.upstream = viewStore._state.eraseToAnyPublisher()
  }

  public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
    // 接收到 `subscriber`,直接对 `upstream` 进行订阅
    self.upstream.subscribe(
      AnySubscriber(
        receiveSubscription: subscriber.receive(subscription:),
        receiveValue: subscriber.receive(_:),
        receiveCompletion: { [viewStore = self.viewStore] in
          subscriber.receive(completion: $0)
          _ = viewStore
        }
      )
    )
  }

  private init<P: Publisher>(
    upstream: P,
    viewStore: Any
  ) where P.Output == Output, P.Failure == Failure {
    self.upstream = upstream.eraseToAnyPublisher()
    self.viewStore = viewStore
  }

  /// 根据给定的 key path 返回某个 State 的 Publisher
  public subscript<Value: Equatable>(
    dynamicMember keyPath: KeyPath<State, Value>
  ) -> StorePublisher<Value> {
    .init(upstream: self.upstream.map(keyPath).removeDuplicates(), viewStore: self.viewStore)
  }
}

private struct HashableWrapper<Value>: Hashable {
  let rawValue: Value
  static func == (lhs: Self, rhs: Self) -> Bool { false }
  func hash(into hasher: inout Hasher) {}
}