簡體   English   中英

如何使用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>

(為什么?因為我想盡可能成為MVVM

到目前為止,我最終寫了很多樣板文,如下所示:

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
    }
}

即:

  1. 使用MutableProperty實例,我必須將其設置為可選,以便能夠初始化它們
  2. 從這些信號中創建信號,即獲取信號發送選項
  3. 它感覺很臟,並且使代碼如此交織在一起,這有點像被動反應的點

任何幫助(A)保持我的信號不可選和(B)優雅地將它們分成2個NoError信號將非常感激。

編輯 - 第二次嘗試

我會盡力在這里回答你的所有問題/意見。

errors = part不起作用flatMapError需要一個SignalProducer(即你的示例代碼只是因為searchStrings是一個Signal字符串,它與我們想要的錯誤相同:它不適用於任何其他類型的輸入)

你是對的,這是因為flatMapError不會改變value類型。 (它的簽名是func flatMapError<F>(_ transform: @escaping (Error) -> SignalProducer<Value, F>) -> SignalProducer<Value, F> )。 如果需要將其更改為其他值類型,則可以在此之后添加另一個要map調用。

結果=部分行為奇怪,因為它在我的現實場景中一旦遇到錯誤(這是我不想要的行為)就終止了信號

是的,這是因為flatMap(.latest)將所有錯誤轉發給外部信號,外部信號上的任何錯誤都將終止它。

好的,所以這里是代碼的更新版本,還有額外的要求

  1. errors類型應該與searchStrings不同,讓我們說Int
  2. 來自MyService.search($0)任何錯誤都不會終止該流

我認為解決這兩個問題的最簡單方法是使用materialize() 它的作用基本上是將所有信號事件(新值,錯誤,終止)“包裝”到Event對象中,然后在信號中轉發該對象。 因此它會將Signal<A, Error>類型的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($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
    }
}

如果有幫助,請告訴我! 我實際上認為它看起來比第一次嘗試更好:)


第一次嘗試

您是否需要訪問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($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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM