我正在看raywenderlich.com上结合使用SwiftUI和Combine:MVVM with Combine Tutorial for iOS的示例。一个ViewModel实现是这样的:
class WeeklyWeatherViewModel: ObservableObject, Identifiable {
// 2
@Published var city: String = ""
// 3
@Published var dataSource: [DailyWeatherRowViewModel] = []
private let weatherFetcher: WeatherFetchable
// 4
private var disposables = Set<AnyCancellable>()
init(weatherFetcher: WeatherFetchable) {
self.weatherFetcher = weatherFetcher
}
}
所以,这对我来说很有意义。在观察模型的视图中,将ViewModel的实例声明为ObservedObject
,如下所示:
@ObservedObject var viewModel: WeeklyWeatherViewModel
然后可以在视图的@Published
定义中利用模型中的body
属性,如下所示:
TextField("e.g. Cupertino", text: $viewModel.city)
在WeeklyWeatherViewModel
中,Combine用于获取city
文本,对其进行请求,然后将其上移至[DailyWeatherRowViewModel]
。到这里为止,一切都是玫瑰花的,而且很有意义。
让我感到困惑的是,然后使用了很多代码:
city
时触发提取。AnyCancellable
用来查找天气数据的信息。dataSource
上的天气查询的输出复制到天气获取发布者上的sink
上它看起来像这样:
// More in WeeklyWeatherViewModel
init(
weatherFetcher: WeatherFetchable,
scheduler: DispatchQueue = DispatchQueue(label: "WeatherViewModel")
) {
self.weatherFetcher = weatherFetcher
_ = $city
.dropFirst(1)
.debounce(for: .seconds(0.5), scheduler: scheduler)
.sink(receiveValue: fetchWeather(forCity:))
}
func fetchWeather(forCity city: String) {
weatherFetcher.weeklyWeatherForecast(forCity: city)
.map { response in
response.list.map(DailyWeatherRowViewModel.init)
}
.map(Array.removeDuplicates)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] value in
guard let self = self else { return }
switch value {
case .failure:
self.dataSource = []
case .finished:
break
}
},
receiveValue: { [weak self] forecast in
guard let self = self else { return }
self.dataSource = forecast
})
.store(in: &disposables)
}
如果我在Combine中寻找@Published
propertyWrapper的定义,似乎所有操作都提供了projectedValue
,即Publisher
,这似乎应该WeeklyWeatherViewModel
可以简单地提供Publisher
来获取天气数据,并让视图直接使用此数据。我不明白为什么需要复制到dataSource
中。
基本上,我期望SwiftUI可以直接利用发布者,并且可以将其发布到View实现的外部,这样我就可以注入它。但是我不知道这是什么。
如果这似乎没有任何意义,那么这个数字,我很困惑。请让我知道,我看看是否可以完善我的解释。谢谢!
答案 0 :(得分:1)
您可以使用m = df.melt(['subject','subject_type'])
n = m['variable'].str.split('_',expand=True).iloc[:,[0,-1]]
n.columns = ['cond','value_type']
m = m.drop('variable',1).assign(**n).sort_values('subject')
并从所有视图模型中重构网络。
如果您觉得本教程的某些部分似乎多余,那是因为。
MVVM 在这种用法中是多余的,并且不能做得更好。
如果您对详细信息感兴趣,我有该教程的重构版本(重构了网络,删除了所有视图模型)。
答案 1 :(得分:0)
我对此没有确切的答案,我也没有找到一种让SwiftUI直接利用Publisher的神奇方法–完全有可能让我感到困惑!
尽管如此,我已经找到了一种合理紧凑和灵活的方法来达到预期的结果。它将sink
的使用减少到附加到输入的单个事件(原始代码中的@Published city
),从而大大简化了取消工作。
这是一个相当通用的模型,具有一个@Published input
属性和一个@Published output
属性(其设置是私有的)。它以转换为输入,并用于转换input
发布者,然后sink
插入输出发布者。 Cancelable
中的sink
已存储。
final class ObservablePublisher<Input, Output>: ObservableObject, Identifiable {
init(
initialInput: Input,
initialOutput: Output,
publisherTransform: @escaping (AnyPublisher<Input, Never>) -> AnyPublisher<Output, Never>)
{
input = initialInput
output = initialOutput
sinkCancelable =
publisherTransform($input.eraseToAnyPublisher())
.receive(on: DispatchQueue.main)
.sink(receiveValue: { self.output = $0 })
}
@Published var input: Input
@Published private(set) var output: Output
private var sinkCancelable: AnyCancellable? = nil
}
如果您想要一种通用性稍差的模型,可以看到将输入(它是发布者)过滤到输出中非常容易。
在视图中,您可以声明模型的实例并像这样使用它:
struct SimpleView: View {
@ObservedObject var model: ObservablePublisher<String, String>
var body: some View {
List {
Section {
// Here's the input to the model taken froma text field.
TextField("Give me some input", text: $model.input)
}
Section {
// Here's the models output which the model is getting from a passed Publisher.
Text(model.output)
}
}
.listStyle(GroupedListStyle())
}
}
这是从“ SceneDelegate.swift”获取的视图及其模型的一些愚蠢的设置。该模型只会延迟键入的内容。
let model = ObservablePublisher(initialInput: "Moo moo", initialOutput: []) { textPublisher in
return textPublisher
.delay(for: 1, scheduler: DispatchQueue.global())
.eraseToAnyPublisher()
}
let rootView = NavigationView {
AlbumSearchView(model: model)
}
我使模型在Input
和Output
上通用。我不知道这在实践中是否真的有用,但似乎可能有用。
我真的很陌生,其中可能存在一些可怕的缺陷,例如效率低下,内存泄漏或保留周期,竞争条件等。不过我还没有找到它们。