Как связать глобальное состояние приложения с представлением модели (используя @ObservedObject)? - PullRequest
1 голос
/ 04 февраля 2020

Я работаю над проектом SwiftUI, в котором у меня есть централизованная архитектура состояния приложения (похожая на Redux). Этот класс состояния приложения имеет тип ObservableObject и напрямую связан с классами представления SwiftUI с помощью @ EnvironmentObject.

Вышеприведенное хорошо работает для небольших приложений. Но по мере того, как иерархия представлений становится все более и более сложной, начинают возникать проблемы с производительностью. Причина в том, что ObservableObject запускает обновление для каждого представления, которое подписалось, даже если для представления может потребоваться только одно свойство.

Моя идея решить эту проблему - поместить модельное представление между глобальным состоянием приложения и представлением. Представление модели должно иметь подмножество свойств глобального состояния приложения, которые используются определенным представлением c. Он должен подписаться на глобальное состояние приложения и получать уведомления о каждом изменении. Но само представление модели должно вызывать только обновление представления для изменения подмножества глобального состояния приложения.

Таким образом, я должен был бы соединить два наблюдаемых объекта (глобальное состояние приложения и представление модели). ). Как это можно сделать?

Вот эскиз:

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 голосов
/ 05 февраля 2020

Упомянутый вами "мост" часто называют Производное состояние .

Вот подход к реализации компонента 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])
  }
}

Вы можете увидеть полную реализацию здесь

0 голосов
/ 04 февраля 2020

Здесь возможен подход

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