ReactiveSwift Обновить данные
Я начинающий в ReactiveSwift. Это выборка кода в моей модели представления:
private let viewDidLoadProperty = MutableProperty<Void?>(nil)
public func viewDidLoad() {
disposables += self.weatherFetcher.fetchCurrentWeather().startWithResult { (result) in
switch result {
case .success(let value):
_ = value?.list?
.map { [weak self] weatherData in
if let weather = weatherData.weather?.first {
self?.weatherFetcher.fetchWeatherImage(icon: weather.icon).startWithResult { (result) in
switch result {
case .success(let iconData):
self?.cellViewModels.append(WeatherCellViewModel(with: weatherData, iconData: iconData))
case .failure(let error):
print("something went wrong - \(error)")
}
}
} else {
self?.cellViewModels.append(WeatherCellViewModel(with: weatherData, iconData: nil))
}
}
case .failure(let error):
print(error)
}
}
self.viewDidLoadProperty.value = ()
}
Когда viewDidLoad вызывается в ViewController, тогда модель представления начинает извлекать данные. Как сказать VC, что fetch является концом и что можно вызвать refreshData? Есть какая-нибудь возможность поймать конец функции viewDidLoad, я имею ввиду после выборки.
initCode:
init(weatherFetcher: WeatherFetcher) {
self.weatherFetcher = weatherFetcher
didStartLoadingWeather = self.viewDidLoadProperty.signal.skipNil()
}
2 ответа
Прежде всего, я бы посоветовал вам использовать ViewModel, которая будет отвечать за выполнение этих операций от имени UIViewController
,
Отвечая на ваш вопрос напрямую. Вам придется использовать какой-то механизм для хранения данных. Это может быть Property
или же MutableProperty
, Мой совет для первых. Вам также понадобится триггер, поэтому, когда viewDidLoad
бывает, вы можете сообщить это. Предполагая, что у вас есть ViewModel:
import ReactiveSwift
import enum Result.NoError
public enum ViewState<T> {
case loading
case loaded([T])
case failure(YourError)
}
class ViewModel {
private let (signal, observer) = Signal<Void, NoError>.pipe()
let state: Property<ViewState<WeatherCellViewModel>>
init() {
let fetch = signal.flatMap(.latest, transform: fetcher)
self.state = Property.init(initial: .loading then: fetch)
}
func fetch() {
observer.send(value: ())
}
}
Я оставлю завершение fetch
для тебя. Но:
- Этот подход позволяет вам сохранять состояние вокруг (через свойство) и позволяет использовать внешний триггер.
- Вы бы сейчас прочитали значения из
state
, fetcher
создается во время инициализации и срабатывает только послеfetch
функция называется.
Хорошей практикой для загрузки табличных представлений с использованием ReactiveSwift (если это ваш случай) является простое связывание данных табличного представления с MutableProperty<[ваши данные]>
Вы просто извлекаете данные и, когда значение получаете, перезагружаете табличное представление. После этого представление таблицы будет волшебно обновлено.
по вашему мнению модель:
struct WeatherInfo {
var temp: Int
var icon: String
}
var data: MutableProperty<[WeatherInfo]>([])
func fetch() -> SignalProducer<Bool, SomeError> {
return weatherFetcher.fetchCurrentWeather().on(value: { val in
let mapped = myCustomMapFunc(val) //transforms 'val' to [WeatherInfo]
self.data.swap(mapped)
})
}
на ваш взгляд контроллер:
let viewModel = MyViewModel()
func viewDidLoad() {
viewModel.fetch().startWithCompleted {
self.tableView.reloadData()
}
}
public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.data.value.count
}
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCellId", for:indexPath) as! MyCustomCell
let info = viewModel.data.value[indexPath.row]
cell.txtTemp.text = info.temp
cell.icon = info.icon
return cell
}
Если это не табличное представление, просто придерживайтесь идеи и делайте это с тем, что вы хотите. Например, если это простая ячейка, загрузите ячейку с новыми данными:
по вашему мнению модель:
// var data: MutableProperty<WeatherInfo>([]) //you don't need it anymore
func fetch() -> SignalProducer<WethearInfo, SomeError> {
return weatherFetcher.fetchCurrentWeather().map({
return myCustomMapFunc($0) //transforms the input into WeatherInfo
})
}
на ваш взгляд контроллер:
let viewModel = MyViewModel()
func viewDidLoad() {
viewModel.fetch().startWithValues { val in
self.reloadMyCell(info: val)
}
}
func reloadMyCell(info: WeatherInfo) {
mycell.temp = info.temp
mycell.icon = info.icon
}