Предположим, у нас есть RootView
и DetailView
. DetailView
имеет свой собственный BindableObject, назовем его DetailViewModel
, и у нас есть сценарий:
RootView
может быть обновлено каким-либо глобальным событием, например, пропущенным подключением к Интернету или его собственными данными /просмотреть модель - Когда
RootView
обрабатывает событие, его содержимое обновляется, и это вызывает создание новой структуры DetailView
- Если
DetailViewModel
создается DetailView
при инициализации,была бы другая ссылка на DetailViewModel
, и ее состояние (например, выбранный объект) будет пропущено
Как мы можем избежать этой ситуации?
- Сохранить все ViewModels как EnvironmentObjectsэто в основном синглтон пул. Этот подход приводит к тому, что ненужные объекты сохраняются в памяти, когда они не используются
- Передача бросить все ViewModels из RootView его потомкам и потомкам потомков (имеет минусы, как указано выше + болезненные зависимости)
- Хранение Просмотр независимых DataObjects (рабочих) как EnvironmentObjects. В таком случае, где мы храним зависимые от вида состояния, которые соответствуют Model? Если мы сохраним его в View, он окажется в ситуации, когда мы взаимно меняем @States, что запрещено SwiftUI
- Лучший подход?
Извините, что не предоставил никакого кода. Этот вопрос касается архитектурной концепции Swift UI, в которой мы пытаемся объединить декларативные структуры и ссылочных объектов с данными .
Пока я не вижу способа сохранить ссылки, которые соответствуют только соответствующему представлению, и не сохранять их в памяти / среде навсегда в их текущих состояниях.
Обновление:
Позволяет добавить некоторый код, чтобы увидеть, что происходит, если виртуальная машина создается ее представлением.
import SwiftUI
import Combine
let trigger = Timer.publish(every: 2.0, on: .main, in: .default)
struct ContentView: View {
@State var state: Date = Date()
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: ContentDetailView(), label: {
Text("Navigation push")
.padding()
.background(Color.orange)
})
Text("\(state)")
.padding()
.background(Color.green)
ContentDetailView()
}
}
.onAppear {
_ = trigger.connect()
}
.onReceive(trigger) { (date) in
self.state = date
}
}
}
struct ContentDetailView: View {
@ObservedObject var viewModel = ContentDetailViewModel()
@State var once = false
var body: some View {
let vmdesc = "View model uuid:\n\(viewModel.uuid)"
print("State of once: \(once)")
print(vmdesc)
return Text(vmdesc)
.multilineTextAlignment(.center)
.padding()
.background(Color.blue)
.onAppear {
self.once = true
}
}
}
class ContentDetailViewModel: ObservableObject, Identifiable {
let uuid = UUID()
}
Обновление 2:
Кажется, что если мы сохраняем ObservableObject как @State в представлении (не как ObservedObject) View сохраняет ссылку на VM
@State var viewModel = ContentDetailViewModel()
Есть ли какие-либо негативные эффекты? Можем ли мы использовать его следующим образом?
Обновление 3:
Кажется, что если ViewModel хранится в @State: View:
- , а ViewModel сохраняется при закрытии с сильнымссылка - deinit никогда не будет выполняться -> утечка памяти
- и ViewModel сохраняется при закрытии со слабой ссылкой - deinit вызывается каждый раз при обновлении представления, все
subs
будут сброшены, но свойства будут одинаковыми
Меххх ...
Обновление 4:
Этот подход также позволяет сохранять сильные ссылки в замыканиях привязок
import Foundation
import Combine
import SwiftUI
/**
static func instanceInView() -> UIViewController {
let vm = ContentViewModel()
let vc = UIHostingController(rootView: ContentView(viewModel: vm))
vm.bind(uiViewController: vc)
return vc
}
*/
public protocol ViewModelProtocol: class {
static func instanceInView() -> UIViewController
var bindings: Set<AnyCancellable> { get set }
func onAppear()
func onDisappear()
}
extension ViewModelProtocol {
func bind(uiViewController: UIViewController) {
uiViewController.publisher(for: \.parent)
.sink(receiveValue: { [weak self] (parent) in
if parent == nil {
self?.bindings.cancel()
}
})
.store(in: &bindings)
}
}
struct ModelView<ViewModel: ViewModelProtocol>: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<ModelView>) -> UIViewController {
return ViewModel.instanceInView()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<ModelView>) {
//
}
}
struct RootView: View {
var body: some View {
ModelView<ParkingViewModel>()
.edgesIgnoringSafeArea(.vertical)
}
}