如何将全局应用程序状态桥接到模型视图(使用@ObservedObject)?

时间:2020-02-04 10:22:28

标签: swiftui combine

我正在开发一个SwiftUI项目,其中有一个集中化的应用程序状态架构(类似于Redux)。此应用程序状态类的类型为ObservableObject,并直接使用@EnvironmentObject绑定到SwiftUI视图类。

以上内容适用于小型应用程序。但是随着视图层次结构变得越来越复杂,开始出现性能问题。原因是,即使视图可能只需要一个属性,ObservableObject也会向已订阅的每个视图触发更新。

我解决这个问题的想法是在全局应用程序状态和视图之间放置一个模型视图。模型视图应具有全局应用程序状态的属性子集,这些属性由特定视图使用。它应该订阅全局应用程序状态并接收每次更改的通知。但是模型视图本身仅应触发视图更新以更改全局应用程序状态的子集。

因此,我将不得不在两个可观察对象(全局应用程序状态和模型视图)之间架起桥梁。该怎么办?

这是草图:

class AppState: ObservableObject {
  @Published var propertyA: Int
  @Published var propertyB: Int
}

class ModelView: ObservableObject {
  @Published var propertyA: Int
}

struct DemoView: View {
  @ObservedObject private var modelView: ModelView

  var body: some View {
    Text("Property A has value \($modelView.propertyA)")
  }
}

2 个答案:

答案 0 :(得分:0)

这是可行的方法

class ModelView: ObservableObject {
    @Published var propertyA: Int = 0

    private var subscribers = Set<AnyCancellable>()

    init(global: AppState) {
        global.$propertyA
            .receive(on: RunLoop.main)
            .assign(to: \.propertyA, on: self)
            .store(in: &subscribers)
    }
}

答案 1 :(得分:0)

您提到的“桥梁”通常称为派生状态

这是一种实现redux Connect组件的方法。当派生状态更改时,它会重新渲染...

public typealias StateSelector<State, SubState> = (State) -> SubState

public struct Connect<State, SubState: Equatable, Content: View>: View {

  // MARK: Public
  public var store: Store<State>
  public var selector: StateSelector<State, SubState>
  public var content: (SubState) -> Content

  public init(
    store: Store<State>,
    selector: @escaping StateSelector<State, SubState>,
    content: @escaping (SubState) -> Content)
  {
    self.store = store
    self.selector = selector
    self.content = content
  }

  public var body: some View {
    Group {
      (state ?? selector(store.state)).map(content)
    }.onReceive(store.uniqueSubStatePublisher(selector)) { state in
      self.state = state
    }
  }

  // MARK: Private
  @SwiftUI.State private var state: SubState?
}

要使用它,请传入商店和“选择器”,它们会将应用程序状态转换为派生状态:

struct MyView: View {
  var index: Int

  // Define your derived state
  struct MyDerivedState: Equatable {
    var age: Int
    var name: String
  }

  // Inject your store
  @EnvironmentObject var store: AppStore

  // Connect to the store
  var body: some View {
    Connect(store: store, selector: selector, content: body)
  }

  // Render something using the selected state
  private func body(_ state: MyDerivedState) -> some View {
    Text("Hello \(state.name)!")
  }

  // Setup a state selector
  private func selector(_ state: AppState) -> MyDerivedState {
    .init(age: state.age, name: state.names[index])
  }
}

您可以看到完整的实现here