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

Swiftui 如何将全局应用程序状态连接到模型视图(使用@ObservedObject)?,swiftui,combine,Swiftui,Combine,我正在从事一个SwiftUI项目,其中我有一个集中的应用程序状态架构(类似于Redux)。此应用程序状态类属于ObserveObject类型,并使用@EnvironmentObject直接绑定到SwiftUI视图类 上述方法适用于小型应用程序。但随着视图层次结构变得越来越复杂,性能问题开始出现。原因是,即使视图可能只需要一个属性,ObserveObject也会对已订阅的每个视图进行更新 我解决这个问题的想法是在全局应用程序状态和视图之间放置一个模型视图。模型视图应该具有全局应用程序状态的属性子集

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

上述方法适用于小型应用程序。但随着视图层次结构变得越来越复杂,性能问题开始出现。原因是,即使视图可能只需要一个属性,ObserveObject也会对已订阅的每个视图进行更新

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

因此,我必须在两个可观察对象(全局应用程序状态和模型视图)之间架起桥梁。如何做到这一点

这是一张草图:

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)")
  }
}

这是可能的方法

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)
    }
}
类模型视图:observeObject{
@已发布的变量propertyA:Int=0
private var subscribers=Set()
初始化(全局:AppState){
全球。$propertyA
.receive(打开:RunLoop.main)
.assign(到:\.propertyA,在:self上)
.store(位于订阅服务器(&S)
}
}
您提到的“桥”通常被称为派生状态

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

publicTypeAlias状态选择器=(状态)->SubState
公共结构连接:视图{
//马克:公众
公共存储:存储
公共变量选择器:状态选择器
公共变量内容:(子状态)->内容
公共初始化(
店:店,,
选择器:@转义状态选择器,
内容:@escaping(SubState)->content)
{
self.store=store
self.selector=选择器
self.content=内容
}
公共机构:一些看法{
团体{
(状态??选择器(store.state)).map(内容)
}.onReceive(store.uniqueSubStatePublisher(选择器)){中的状态
self.state=状态
}
}
//马克:二等兵
@SwiftUI.State私有变量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])
  }
}

您可以看到完整的实施

谢谢,这看起来很有希望。当我再次考虑上述建议的解决方案时,我不确定它是否能解决主要问题:即使只更改了propertyB,全局状态也会触发propertyA的更新。然后,“分配”会将未更改的propertyA值存储到模型视图自己的propertyA,这将触发视图的更新。是这样吗?不,这是一个属性发布服务器的显式订阅服务器。太好了。还有一个问题:您知道Combine框架是否通过引用传递这些对象吗?我这样问是因为如果propertyA是一个大数组,出于内存方面的考虑,我不希望在视图模型中有它的真实副本。Swift显式地只对字典/集合使用写时拷贝。对于其他类型,文档提到您可以自己实现它。实际应用程序中的视图是许多不同视图的组合。SwiftUI将仅“更新”依赖于您的模型的视图,并且仅当模型中的值发生更改时。这正是你想要的,不是吗?