简体   繁体   English

如何使用ReactiveSwift将有错误的信号转换为NoError? (优雅)

[英]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: 大多数时候,我的信号生成器是网络调用的结果,所以我想将结果分成两种情况:

  • if a value is available, send a Signal<A, NoError> 如果值可用,则发送Signal<A, NoError>
  • if an error happened, send a 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: 即:

  1. using MutableProperty instances, that I have to set as optional to be able to initialize them 使用MutableProperty实例,我必须将其设置为可选,以便能够初始化它们
  2. creating signals from those, ie getting a signal sending optionals as well 从这些信号中创建信号,即获取信号发送选项
  3. it feels dirty and makes the code so intertwined it kind of ruins the point of being reactive 它感觉很脏,并且使代码如此交织在一起,这有点像被动反应的点

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信号将非常感激。

Edit - Second Attempt 编辑 - 第二次尝试

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 好的,所以这里是代码的更新版本,还有额外的要求

  1. errors should have different type than searchStrings , let's say Int errors类型应该与searchStrings不同,让我们说Int
  2. Any error from 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 :) 我实际上认为它看起来比第一次尝试更好:)


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.

相关问题 如何绑定信号 <Bool, NoError> 在Reactive Cocoa 4中启用UIButton的属性 - How to bind Signal<Bool, NoError> to enabled property of UIButton in Reactive Cocoa 4 如何转换“SignalProducer <Bool, NoError> “到”SignalProducer <Bool, NSError> “ReactiveCocoa 3? - How can I convert “SignalProducer<Bool, NoError>” to “SignalProducer<Bool, NSError>” of ReactiveCocoa 3? ReactiveSwift 一对多信号订阅和相关的 memory 开销 - ReactiveSwift one vs multiple signal subscriptions and related memory overhead 如何在ReactiveSwift中观察UIControl子类中的更改? - How do I observe changes in a UIControl subclass in ReactiveSwift? 我如何订阅一次信号并有条件地启动次要信号,而又不触发原始信号两次? - How can I subscribe to a one-time signal and conditionally initiate a secondary signal without having the original signal fired twice? ReactiveSwift:如何编写任务调度程序 - ReactiveSwift: How to write a Task Scheduler 在ReactiveSwift中对信号的跳过(while :)调用被忽略 - Skip(while:) call against a Signal in ReactiveSwift is being ignored 如何通知一个线程等待下载完成,然后将数据传递给等待线程 - How can I signal one thread to wait until a download is finished and after that pass the data to waiting thread 如何在ReactiveCocoa(Swift)中将NSError SignalProducer转换为NoError SignalProducer - How to Convert NSError SignalProducer to NoError SignalProducer in ReactiveCocoa (Swift) 如何将 UIButton 转换为圆形? - How can I transform UIButton to circle?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM