如何使用 ReactiveSwift 将有错误的信号转换为 NoError 信号? (并且要优雅)
How can I transform a signal with errors into a NoError one with ReactiveSwift? (and be elegant)
将我的 ReactiveSwift 的 SignalProducer<A, NetworkError>
转换为 Signal<A, NoError>
的最优雅的方法是什么?
大多数时候,我的信号生成器是网络调用的结果,所以我想将结果分为两种情况:
- 如果有可用值,请发送
Signal<A, NoError>
- 如果发生错误,请发送
Signal<String, NoError>
以及错误的本地化描述
(为什么?因为我正在尝试 be as MVVM as possible)
到目前为止,我最终写了很多样板,如下所示:
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
}
}
即:
- 使用
MutableProperty
个实例,我必须将其设置为可选才能初始化它们
- 从这些信号中创建信号,即获得发送可选信号的信号
- 感觉很脏,让代码交织在一起,有点破坏了反应的意义
关于 (A) 保持我的信号非可选和 (B) 将它们分成 2 NoError
信号的任何帮助将不胜感激。
编辑 - 第二次尝试
我会尽量在这里回答你所有的问题/评论。
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)
你是对的,这是因为 flatMapError
没有改变 value
类型。 (它的签名是func flatMapError<F>(_ transform: @escaping (Error) -> SignalProducer<Value, F>) -> SignalProducer<Value, F>
)。如果您需要将其更改为另一种值类型,则可以在此之后添加对 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
是的,这是因为flatMap(.latest)
将所有错误转发给外部信号,外部信号上的任何错误都会终止它。
好的,这是代码的更新版本,具有额外的要求
errors
的类型应该与 searchStrings
不同,比方说 Int
- 来自
MyService.search([=20=])
的任何错误都不会终止流
我认为解决这两个问题的最简单方法是使用 materialize()
。它所做的基本上是 "wrap" 所有信号事件(新值、错误、终止)到一个 Event
对象中,然后在信号中转发这个对象。因此它将 Signal<A, Error>
类型的信号转换为 Signal<Event<A, Error>, NoError>
(您可以看到返回的信号不再有错误,因为它被包裹在 Event
中)。
在我们的例子中,这意味着您可以使用它轻松地防止信号在发出错误后终止。如果错误包含在 Event
中,那么它不会自动终止发送它的信号。 (实际上,只有调用 materialize()
的信号完成,但我们会将其包装在 flatMap
中,因此外部信号不应完成。)
这是它的样子:
// 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([=10=]).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
}
}
如果有帮助请告诉我!我实际上认为它看起来比第一次尝试更好:)
第一次尝试
您是否需要访问您的 viewModel 的状态,或者您是否正在尝试完全无状态?如果是无状态的,你不需要任何属性,你可以这样做
// 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([=11=])
}
// 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)
}
将我的 ReactiveSwift 的 SignalProducer<A, NetworkError>
转换为 Signal<A, NoError>
的最优雅的方法是什么?
大多数时候,我的信号生成器是网络调用的结果,所以我想将结果分为两种情况:
- 如果有可用值,请发送
Signal<A, NoError>
- 如果发生错误,请发送
Signal<String, NoError>
以及错误的本地化描述
(为什么?因为我正在尝试 be as MVVM as possible)
到目前为止,我最终写了很多样板,如下所示:
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
}
}
即:
- 使用
MutableProperty
个实例,我必须将其设置为可选才能初始化它们 - 从这些信号中创建信号,即获得发送可选信号的信号
- 感觉很脏,让代码交织在一起,有点破坏了反应的意义
关于 (A) 保持我的信号非可选和 (B) 将它们分成 2 NoError
信号的任何帮助将不胜感激。
编辑 - 第二次尝试
我会尽量在这里回答你所有的问题/评论。
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)
你是对的,这是因为 flatMapError
没有改变 value
类型。 (它的签名是func flatMapError<F>(_ transform: @escaping (Error) -> SignalProducer<Value, F>) -> SignalProducer<Value, F>
)。如果您需要将其更改为另一种值类型,则可以在此之后添加对 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
是的,这是因为flatMap(.latest)
将所有错误转发给外部信号,外部信号上的任何错误都会终止它。
好的,这是代码的更新版本,具有额外的要求
errors
的类型应该与searchStrings
不同,比方说Int
- 来自
MyService.search([=20=])
的任何错误都不会终止流
我认为解决这两个问题的最简单方法是使用 materialize()
。它所做的基本上是 "wrap" 所有信号事件(新值、错误、终止)到一个 Event
对象中,然后在信号中转发这个对象。因此它将 Signal<A, Error>
类型的信号转换为 Signal<Event<A, Error>, NoError>
(您可以看到返回的信号不再有错误,因为它被包裹在 Event
中)。
在我们的例子中,这意味着您可以使用它轻松地防止信号在发出错误后终止。如果错误包含在 Event
中,那么它不会自动终止发送它的信号。 (实际上,只有调用 materialize()
的信号完成,但我们会将其包装在 flatMap
中,因此外部信号不应完成。)
这是它的样子:
// 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([=10=]).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
}
}
如果有帮助请告诉我!我实际上认为它看起来比第一次尝试更好:)
第一次尝试
您是否需要访问您的 viewModel 的状态,或者您是否正在尝试完全无状态?如果是无状态的,你不需要任何属性,你可以这样做
// 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([=11=])
}
// 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)
}