#ios #swift #xcode #swiftui #combine
Вопрос:
У меня есть ObservableObject
, в котором внутри есть еще два ObservableObjects
. У каждого из этих двух ObservableObject
есть одно или несколько @Published
свойств внутри. Мое решение работает, но я на 100% уверен, что должен быть другой способ. Я должен скопировать/ вставить все, чтобы заставить его работать. Я попытался поместить этих двух издателей в представление, но затем мне нужно передать много параметров в дочернее представление. Есть идеи, как это упростить?
class PackageService: ObservableObject {
@Published var package: Package
@Published var error: Error?
@Published var distance: Double?
@Published var expectedTravelTime: String?
@Published var amount: Int = 0
@Published var cost: Double = 0
@Published var annotations = [MKPointAnnotation]()
@Published var isCalculating = true
@Published var route: MKRoute?
var amountService = AmountService()
var routeService = RouteService()
private var cancellables = Set<AnyCancellable>()
init(package: Package) {
self.package = package
routeService.calcDirections(source: package.source.toCLLocationCoordinate2D, destination: package.destination.toCLLocationCoordinate2D)
routeService.$route.sink { [self] route in
self.route = route
amountService.calc(distance: route?.distance)
calc(route: route)
}
.store(in: amp;cancellables)
routeService.$isCalculating.sink { [self] isCalculating in
self.isCalculating = isCalculating
}
.store(in: amp;cancellables)
routeService.$error.sink { [self] error in
self.error = error
}
.store(in: amp;cancellables)
amountService.$amount.sink { [self] amount in
self.amount = amount
}
.store(in: amp;cancellables)
amountService.$cost.sink { [self] cost in
self.cost = cost
}
.store(in: amp;cancellables)
amountService.$error.sink { [self] error in
self.error = error
}
.store(in: amp;cancellables)
}
Ответ №1:
Похоже PackageService
, что это всего лишь тонкая оболочка вокруг двух других сервисов.
Имело бы смысл использовать вычисляемые свойства вместо подписки и публикации изменений для каждого свойства:
var isCalculating: Bool { routeService.isCalculating }
var amount: Int { amountService.amount }
// etc...
и подписаться один раз, чтобы сообщить об изменениях в представлении:
routeService.objectWillChange
.merge(with: amountService.objectWillChange)
.sink(receiveValue: self.objectWillChange.send)
.store(in: amp;cancellables)
Кроме того, рассмотрите возможность упаковки различных свойств в единую модель данных, например:
struct AmountModel {
var amount: Int
var cost: Double
}
и у AmountService
них есть одно свойство. Это может быть Result<AmountModel, Error>
для захвата данных или ошибки, или, если вам нужно, включите свойство Error
как свойство.
Комментарии:
1. Отличный ответ, я знал, что его можно упростить.
2. но как я реагирую на изменения внутри
routeService
, так как при обновлении свойства внутри routeService мне нужно запуститьamountService.calc(distance: route?.distance)
3. @M1X, если вам нужно выполнить какое-то действие, не связанное со SwiftUI, то да, вы могли бы наблюдать с помощью Combine, например
route.$distance.sink
… но вы спрашивали о наблюдении в SwiftUI. Все, что нужно SwiftUI (это также отвечает на вопрос вашего второго комментария), — это знать, что что-то изменилось (см..sink(receiveValue: self.objectWillChange.send)
В моем ответе), а затем он перезагружает представление. Итак, вы могли бы сделать.onChange(packageSvc.distance) { packageSvc.recalcAmount() }
4. Хорошо, я думаю, что понял, что это
.sink(receiveValue: self.objectWillChange.send)
уведомляет о представлении..onChange(packageSvc.distance) { packageSvc.recalcAmount() }
чтобы уловить изменения в представлении. И если я хочу выполнить какое-то действие, которое мне нужно использоватьsink
.5. Также странно, но вычисляемое свойство, которое возвращает a
@Published
, будет отражать изменения в представлении даже без .sink(receiveValue: self.objectWillChange.send)