ReactiveSwift: как подписаться на SignalProducer?
Я пытаюсь узнать ReactiveSwift и ReactiveCocoa. я могу использовать Signal
а также Property
довольно хорошо, но у меня проблемы с SignalProducer
,
Как я понимаю, SignalProducer
идеально подходит для таких вещей, как сетевые запросы. Я настроил свой уровень API для создания и возврата провайдера сигналов, который может запустить вызывающий.
class ApiLayer {
func prepareRequest(withInfo info: RequestInfo) -> SignalProducer<ModelType, ErrorType> {
return SignalProducer<ModelType, ErrorType> { (observer, lifetime) in
// Send API Request ...
// In Completion Handler:
let result = parseJson(json)
observer.send(value: result)
observer.sendCompleted()
}
}
}
Но как мне ожидать результатов?
Я пробовал что-то вроде этого, но я получаю ошибку, поэтому я должен делать / думать об этом неправильно.
apiLayer.prepareRequest(withInfo: info)
.startWithValues { (resultModel) in
// Do Stuff with result ...
}
Вот ошибка, которую я получаю:
Неоднозначная ссылка на член 'startWithValues'
- Найден этот кандидат (ReactiveSwift.SignalProducer
) - Найден этот кандидат (ReactiveSwift.SignalProducer
)
РЕДАКТИРОВАТЬ
Я пытался быть более явным, чтобы помочь компилятору определить правильный метод, вот так. Но ошибка все еще остается.
apiLayer.prepareRequest(withInfo: info)
.startWithValues { (resultModel: ModelType) in // Tried adding type. Error remained.
// Do Stuff with result ...
}
РЕДАКТИРОВАТЬ 2
После получения справки на странице поддержки GitHub и обдумывания предоставленного здесь ответа, вот что я закончил.
Одно из ключевых отличий от моих предыдущих попыток заключается в том, что вызывающий не запускает вручную SignalProducer
, Скорее, создавая его внутри / в ответ на другой сигнал, он неявно запускается внутри цепочки.
Ранее я (неправильно) предполагал, что необходимо извлечь и явно подписаться на Signal
это SignalProducer
"Производство".
Вместо этого я сейчас думаю о SignalProducer
Это просто отложенная работа, которая запускается в ответ на стимулы. Я могу вручную подписаться на SignalProvider
или я могу позволить другому Signal
вместо этого предоставьте этот стимул. (Последний использовался в моем обновленном образце ниже. Кажется, он довольно чистый и намного более FRP-похожий, чем ручной запуск, который я перенес из своего императивного мышления.)
enum ErrorType: Error {
case network
case parse
}
class ApiLayer {
func prepareRequest(withInfo info: RequestInfo) -> SignalProducer<ModelType, ErrorType> {
let producer = SignalProducer<ResultType, NoError> { (observer, lifetime) in
sendRequest(withInfo: info) { result in
observer.send(value: result)
observer.sendCompleted()
}
}
return producer
.attemptMap { result throws -> ResultType in
let networkError: Bool = checkResult(result)
if (networkError) {
throw ErrorType.network
}
}
.retry(upTo: 2)
.attemptMap { result throws -> ModelType in
// Convert result
guard let model: ModelType = convertResult(result) else {
throw ErrorType.parse
}
return model
}
// Swift infers AnyError as some kind of error wrapper.
// I don't fully understand this part yet, but to match the method's type signature, I needed to map it.
.mapError { $0.error as! ErrorType}
}
}
// In other class/method
// let apiLayer = ApiLayer(with: ...)
// let infoSignal: Signal<RequestInfo, NoError> = ...
infoSignal
.flatMap(.latest) { (info) in
apiLayer.prepareRequest(withInfo: info)
}
.flatMapError { error -> SignalProducer<ModelType, NoError> in
// Handle error
// As suggested by the ReactiveSwift documentation,
// return empty SignalProducer to map/remove the error type
return SignalProducer<ModelType, NoError>.empty
}
.observeValues { model in
// Do stuff with result ...
}
2 ответа
ReactiveSwift
Философия заключается в том, что пользователям не должно быть легко игнорировать ошибки. Так startWithValues
доступно только если тип ошибки производителя NoError
, что гарантирует отсутствие ошибок. Если ваш производитель может отправить ошибку, вам нужно использовать такую функцию, как startWithResult
что позволит вам справиться с этим:
apiLayer.prepareRequest(withInfo: info).startWithResult { result in
switch result {
case let .success(model):
// Do stuff with model
case let .failure(error):
// Handle error
}
}
Игнорировать ошибки не очень хорошая идея, но в некоторых случаях их можно рассматривать как nil-значения с таким расширением:
public extension SignalProducer {
func skipErrors() -> SignalProducer<Value?, NoError> {
return self
.flatMap(.latest, { SignalProducer<Value?, NoError>(value: $0) })
.flatMapError { _ in SignalProducer<Value?, NoError>(value: nil) }
}
}