У меня есть минимальный рабочий пример чего-то, в чем я до сих пор не уверен:
import SwiftUI
struct Car {
var name: String
}
class DataModel: ObservableObject {
@Published var cars: [Car]
init(_ cars: [Car]) {
self.cars = cars
}
}
struct TestList: View {
@EnvironmentObject var dataModel: DataModel
var body: some View {
NavigationView {
List(dataModel.cars, id: \.name) { car in
NavigationLink(destination: TestDetail(car: car).environmentObject(self.dataModel)) {
Text("\(car.name)")
}
}
}
}
}
struct TestDetail: View {
@EnvironmentObject var dataModel: DataModel
var car: Car
var carIndex: Int {
dataModel.cars.firstIndex(where: {$0.name == self.car.name})!
}
var body: some View {
Text(car.name)
.onTapGesture {
self.dataModel.cars[self.carIndex].name = "Changed Name"
}
}
}
struct TestList_Previews: PreviewProvider {
static var previews: some View {
TestList().environmentObject(DataModel([.init(name: "A"), .init(name: "B")]))
}
}
Речь идет об использовании структур в качестве моделей данных. Пример похож на официальный учебник Apple по SwiftUI
(https://developer.apple.com/tutorials/swiftui/handling-user-input).
По сути, у нас есть класс DataModel
, который передается по дереву как EnvironmentObject
, Класс охватывает базовые типы данных c нашей модели. В данном случае это массив структуры Car
:
class DataModel: ObservableObject {
@Published var cars: [Car]
...
}
. Пример состоит из простого списка, в котором указаны названия всех автомобилей. Когда вы нажимаете на один, вы попадаете в подробный вид. Подробному представлению передается свойство car
как (в то время как dataModel
передается как EnvironmentObject
):
NavigationLink(destination: TestDetail(car: car).environmentObject(self.dataModel)) {
Text("\(car.name)")
}
Свойство car
подробного представления используется для его заполнения. Однако, если вы хотите, например, изменить имя car
в подробном представлении, вам нужно от go до dataModel
, поскольку car
- это просто копия исходного экземпляра, найденного в dataModel
. Таким образом, сначала вы должны найти индекс автомобиля в массиве cars
dataModel, а затем обновить его:
struct TestDetail: View {
...
var carIndex: Int {
dataModel.cars.firstIndex(where: {$0.name == self.car.name})!
}
...
self.dataModel.cars[self.carIndex].name = "Changed Name"
Это не похоже на отличное решение. Поиск по индексу - это линейная операция, которую вы должны выполнять всякий раз, когда хотите что-то изменить (массив может измениться в любое время, поэтому вы должны постоянно повторять поиск по индексу).
Кроме того, это означает, что вы есть повторяющиеся данные. Свойство car
подробного вида точно отражает car
viewModel
. Это разделяет данные. Это не правильно.
Если бы car
было class
вместо struct
, это не было бы проблемой, потому что вы передаете экземпляр как ссылку. Было бы намного проще и чище.
Однако, похоже, что каждый хочет использовать структуры для этих вещей. Конечно, они безопаснее, с ними не может быть циклов ссылок, но это создает избыточные данные и вызывает более дорогие операции. По крайней мере, для меня это выглядит так.
Мне бы очень хотелось понять, почему это вообще может не быть проблемой и почему на самом деле это лучше, чем уроки. Уверен, у меня просто проблемы с пониманием этого как новой концепции.