Обработка сетевых запросов Reactive Cocoa 5 и ReactiveSwift
Я пытаюсь выяснить, можно ли реализовать обработку сетевых запросов в соответствии с моими потребностями, используя ReactiveSwift и RAC5.
В разделе Миграция с RACSignal на ReactiveSwift или RAC5 мне сказали, что это можно сделать с помощью SignalProducer, но более глубокое изучение этого вопроса не дало мне ожидаемых результатов.
Итак, я хотел бы иметь:
1. Каждый раз, когда текст изменяется в textField, отправьте запрос (поиск по ключевому слову).
2. Когда пользователь закрывает текущий ViewController, текущий запрос должен быть автоматически отменен
3. Возможность отмены запроса после изменения ключевого слова.
Вот что у меня есть
self.textField.reactive.continuousTextValues.skipNil().filter({ (value) -> Bool in
return value.characters.count > 0
}).observeValues { [unowned self] (value) in
self.fetchSignalDisposable?.dispose()
self.fetchSignal = self.producerFor(keyword: value).on(started: {
print("started")
}, failed: { (error) in
print("error")
}, completed: {
print("completed")
}, value: { [unowned self] (items) in
print("value")
self.items.append(contentsOf: items)
self.tableView.reloadData()
})
self.fetchSignalDisposable = self.fetchSignal!.start()
}
А вот производитель инициализатора
return SignalProducer<Any, NSError> { (observer, disposable) in
let task: URLSessionDataTask? = NetworkClient.fetchRequestWith(uri: "test", parameters: ["keyword" : keyword], success: { response in
observer.send(value: response)
observer.sendCompleted()
}, failure: { error in
observer.send(error: error)
observer.sendCompleted()
})
disposable += {
task?.cancel()
}
}
Заметки:
1. Иногда я хочу иметь своего рода "блок обоих обработчиков", который будет вызываться как при успехе, так и при ошибках, поэтому такие вещи, как скрытие индикаторов загрузки, могут быть выполнены под этим блоком.
Несколько проблем / вопросов здесь:
1. Как только я закрываю VC (dismiss action), вызывается обработчик наблюдать за значением еще раз. Это можно исправить, добавив .skipRepeats()
, но я думаю, что это просто обходной путь, а не точное решение. Я бы хотел, чтобы этот наблюдатель больше не работал, если я закрою ВК
2. completed
блок не вызывается в случае ошибки, даже если я вызываю его вручную сразу после вызова send(error: error)
3. Если запрос все еще загружается, и я закрываю VC, он не удаляется автоматически, что мне кажется странным. Я думал, что блок dispose будет вызываться автоматически, как только viewController потеряет ссылку на signalProducer. Даже звонит self.fetchSignalDisposable?.dispose()
в deinit
Метод ВК не отменяет запрос. Он все еще заканчивает запрос и звонки value
обработчик, который приводит к падению с ошибкой Bad Access
Мои личные потребности:
1. Иметь какой-то блок "оба", который будет вызываться после успешных и неудачных случаев запроса
2. Все наблюдатели для текстовых значений textField s должны быть удалены и больше не быть активными после закрытия VC
3. Сетевой запрос должен быть отменен сразу после закрытия ВК
PS: Конечно, спасибо всем, кто прочитал этот огромный пост и провел время, помогая мне!
1 ответ
Пример "Выполнение сетевых запросов" из файла readme ReactiveSwift является хорошим примером для такого рода вещей. Вместо того, чтобы использовать observeValues
на вашем текстовом поле сигнала, как правило, вы бы использовали .flatMap(.latest)
чтобы подключить его непосредственно к вашему SignalProducer, вот так (обратите внимание, я не проверял этот код, но, надеюсь, он получил идею):
self.textField.reactive.continuousTextValues
.skipNil()
.filter { (value) -> Bool in
return value.characters.count > 0
}
.flatMap(.latest) { [unowned self] value in
return self.producerFor(keyword: value)
// Handling the error here prevents errors from terminating
// the outer signal. Now a request can fail while allowing
// subsequent requests to continue.
.flatMapError { error in
print("Network error occurred: \(error)")
return SignalProducer.empty
}
}
.observe(on: UIScheduler())
.observe { [unowned self] event in
switch event {
case let .value(items):
print("value")
self.items.append(contentsOf: items)
self.tableView.reloadData()
case let .failed(error):
print("error")
case .completed, .interrupted:
print("completed")
}
}
Определение .latest
приводит к тому, что предыдущий сетевой запрос автоматически отменяется при запуске нового, поэтому нет необходимости отслеживать текущий запрос в глобальной переменной.
Что касается управления временем жизни, трудно сказать, что лучше, не зная вашей более широкой структуры кода. Обычно я хотел бы добавить что-то вроде .take(during: self.reactive.lifetime)
на мой сигнал прекратить подписку, когда self
освобождается, вероятно, прямо перед вызовом observe
,
События ошибок завершают сигналы. Нет необходимости отправлять завершенное событие после ошибки, и наблюдатели все равно его не увидят. В основном, завершение означает, что сигнал завершился успешно, а ошибка означает, что сигнал завершился неудачно.