[英]How can I transform a signal with errors into a NoError one with ReactiveSwift? (and be elegant)
What is the most elegant way to transform my ReactiveSwift's SignalProducer<A, NetworkError>
into a Signal<A, NoError>
? 将ReactiveSwift的SignalProducer<A, NetworkError>
转换为Signal<A, NoError>
的最优雅方法是什么?
Most of the time, my signal producer is the result of a network call, so I want to split the results into two cases: 大多数时候,我的信号生成器是网络调用的结果,所以我想将结果分成两种情况:
Signal<A, NoError>
如果值可用,则发送Signal<A, NoError>
Signal<String, NoError>
with the error's localized description 如果发生错误,请发送带有错误本地化描述的Signal<String, NoError>
(why? because i'm trying to be as MVVM as possible ) (为什么?因为我想尽可能成为MVVM )
So far, I end up writing a lot of boilerplate like the following: 到目前为止,我最终写了很多样板文,如下所示:
let resultsProperty = MutableProperty<SearchResults?>(nil)
let alertMessageProperty = MutableProperty<String?>(nil)
let results = resultsProperty.signal // `Signal<SearchResults?, NoError>`
let alertMessage = alertMessageProperty.signal // `Signal<String?, NoError>`
// ...
searchStrings.flatMap(.latest) { string -> SignalProducer<SearchResults, NetworkError> in
return MyService.search(string)
}
.observe { event in
switch event {
case let .value(results):
resultsProperty.value = results
case let .failed(error):
alertMessageProperty.value = error
case .completed, .interrupted:
break
}
}
ie: 即:
MutableProperty
instances, that I have to set as optional to be able to initialize them 使用MutableProperty
实例,我必须将其设置为可选,以便能够初始化它们 Any help on (A) keeping my signals non optional and (B) splitting them into 2 NoError
signals elegantly would be greatly appreciated. 任何帮助(A)保持我的信号不可选和(B)优雅地将它们分成2个NoError
信号将非常感激。
I will try to answer all your questions / comments here. 我会尽力在这里回答你的所有问题/意见。
The errors = part doesn't work as flatMapError expects a SignalProducer (ie your sample code works just because searchStrings is a Signal string, which coincidently is the same as the one we want for errors: it does not work for any other kind of input) errors = part不起作用flatMapError需要一个SignalProducer(即你的示例代码只是因为searchStrings是一个Signal字符串,它与我们想要的错误相同:它不适用于任何其他类型的输入)
You are correct, this is because flatMapError
does not change the value
type. 你是对的,这是因为flatMapError
不会改变value
类型。 (Its signature is func flatMapError<F>(_ transform: @escaping (Error) -> SignalProducer<Value, F>) -> SignalProducer<Value, F>
). (它的签名是func flatMapError<F>(_ transform: @escaping (Error) -> SignalProducer<Value, F>) -> SignalProducer<Value, F>
)。 You could add another call to map
after this if you need to change it into another value type. 如果需要将其更改为其他值类型,则可以在此之后添加另一个要map
调用。
the results = part behaves weirdly as it terminates the signal as soon as an error is met (which is a behavior I don't want) in my real-life scenario 结果=部分行为奇怪,因为它在我的现实场景中一旦遇到错误(这是我不想要的行为)就终止了信号
Yes, this is because the flatMap(.latest)
forwards all errors to the outer signal, and any error on the outer signal will terminate it. 是的,这是因为flatMap(.latest)
将所有错误转发给外部信号,外部信号上的任何错误都将终止它。
Okay so here's an updated version of the code, with the extra requirements that 好的,所以这里是代码的更新版本,还有额外的要求
errors
should have different type than searchStrings
, let's say Int
errors
类型应该与searchStrings
不同,让我们说Int
MyService.search($0)
will not terminate the flow 来自MyService.search($0)
任何错误都不会终止该流 I think the easiest way to tackle both these issues is with the use of materialize()
. 我认为解决这两个问题的最简单方法是使用materialize()
。 What it does is basically "wrap" all signal events (new value, error, termination) into a Event
object, and then forward this object in the signal. 它的作用基本上是将所有信号事件(新值,错误,终止)“包装”到Event
对象中,然后在信号中转发该对象。 So it will transform a signal of type Signal<A, Error>
into a Signal<Event<A, Error>, NoError>
(you can see that the returned signal does not have an error anymore, since it is wrapped in the Event
). 因此它会将Signal<A, Error>
类型的Signal<A, Error>
转换为Signal<Event<A, Error>, NoError>
(您可以看到返回的信号不再有错误,因为它包含在Event
) 。
What it means in our case is that you can use that to easily prevent signals from terminating after emitting errors. 在我们的例子中,它意味着您可以使用它来轻松防止信号在发出错误后终止。 If the error is wrapped inside an Event
, then it will not automatically terminate the signal who sends it. 如果错误包含在Event
,则它不会自动终止发送它的信号。 (Actually, only the signal calling materialize()
completes, but we will wrap it inside the flatMap
so the outer one should not complete.) (实际上,只有调用materialize()
的信号才会完成,但是我们会将它包装在flatMap
所以外部的不应该完成。)
Here's how it looks like: 这是它的样子:
// Again, I assume this is what you get from the user
let searchStrings: Signal<String, NoError>
// Keep your flatMap
let searchResults = searchStrings.flatMap(.latest) {
// Except this time, we wrap the events with `materialize()`
return MyService.search($0).materialize()
}
// Now Since `searchResults` is already `NoError` you can simply
// use `filterMap` to filter out the events that are not `.value`
results = searchResults.filterMap { (event) in
// `event.value` will return `nil` for all `Event`
// except `.value(T)` where it returns the wrapped value
return event.value
}
// Same thing for errors
errors = searchResults.filterMap { (event) in
// `event.error` will return `nil` for all `Event`
// except `.failure(Error)` where it returns the wrapped error
// Here I use `underestimatedCount` to have a mapping to Int
return event.error?.map { (error) in
// Whatever your error mapping is, you can return any type here
error.localizedDescription.characters.count
}
}
Let me know if that helps! 如果有帮助,请告诉我! I actually think it looks better than the first attempt :) 我实际上认为它看起来比第一次尝试更好:)
Do you need to access the state of you viewModel or are you trying to go full state-less? 您是否需要访问viewModel的状态,或者您是否正在尝试完全无状态? If state-less, you don't need any properties, and you can just do 如果是无状态,则不需要任何属性,您可以这样做
// I assume this is what you get from the user
let searchStrings: Signal<String, NoError>
// Keep your flatMap
let searchResults = searchStrings.flatMap(.latest) {
return MyService.search($0)
}
// Use flatMapError to remove the error for the values
results = searchResults.flatMapError { .empty }
// Use flatMap to remove the values and keep the errors
errors = searchResults.filter { true }.flatMapError { (error) in
// Whatever you mapping from error to string is, put it inside
// a SignalProducer(value:)
return SignalProducer(value: error.localizedDescription)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.