RxSwift:在使用 flatMap 和 reduce 时需要帮助

Posted

技术标签:

【中文标题】RxSwift:在使用 flatMap 和 reduce 时需要帮助【英文标题】:RxSwift: Need help using flatMap and reduce 【发布时间】:2016-08-24 09:36:59 【问题描述】:

我正在编写一个简单的Diceware 密码生成器来试验 RxSwift。 我正在努力在单独的步骤中使用 flatMapreduce

当前代码

我有一个可观察到的wordCount,它绑定到UIStepper 值,并生成具有给定字数的新密码。

    let rawPassword = wordCount
        .asObservable()
        .map  wordCount in
            self.rollDice(numberOfDice: wordCount)
                .map  numbers in wordMap[numbers]! 
        

rollDice 返回一个Observable<String>(例如:["62345", "23423", "14231", ...]),然后映射到单词。

rawPasswordObservable<Observable<String>>

在本例中,它将是:[["spec", "breed", "plins", "wiry", "chile", "cecil"]]

然后我有一个reducedPassword 其中flatMapreduce 到一个String

    let reducedPassword = rawPassword
        .flatMap  raw in
            raw.reduce("")  prev, value in
                let separator = "-"
                return prev == "" ? value : "\(prev)\(separator)\(value)"
            
    

这可行,我最终得到了字符串:spec-breed-plins-wiry-chile-cecil

问题

现在我想从 UI 更改单词分隔符。当UITextField 中的文本更新时,我只想在rawPassword 上重新应用reduce。

我正在尝试使用combineLatest 将分隔符Observable<String> 与我的Observable<Observable<String>> rawPassword 结合起来,如下所示:

    let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable())  raw, sep in
        raw.reduce("")  prev, value in
            return prev == "" ? value : "\(prev)\(sep)\(value)"
        
    

但是reduce 永远不会触发并且单击步进器不会执行任何操作。 我曾尝试在一个单独的步骤中flatMap,但随后,combineLatest 我只说了最后一句话。 combineLatest 是正确的方法吗?

【问题讨论】:

为什么不让分隔符可观察?可能保留最后生成的单词... 我的分隔符是Observable<String>,我只是不知道如何将它与rawPassword结合起来。 我下面的回答不满足问题吗? 【参考方案1】:

问题

问题在于reducedPassword(在您的combineLatest 情况下)实际上是Observable<Observable<String>>。它有助于键入注释,尤其是在学习 RxSwift 以确保事情实际上是您期望的那样时。

let reducedPassword: Observable<Observable<String>> = Observable.combineLatest(rawPassword, separator.asObservable())  // ...

修复

因此,要使修改后的 reducedPassword 正常工作,您需要获取通过 (Observable&lt;String&gt;) 的元素并用它替换当前的 Observable&lt;Observable&lt;String&gt;&gt;。所以使用switchLatest()(这就像在做flatMap $0 ):

let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable()) 
    raw, sep in

    raw.reduce("")  prev, value in
        prev == "" ? value : "\(prev)\(sep)\(value)"
    

.switchLatest() // equivalent to: .flatMap  $0 

齐心协力

我将您的所有代码连同修复程序放在一起,可以在没有 UI 的情况下按原样运行:

let disposeBag = DisposeBag()

let wordCount = BehaviorSubject<UInt>(value: 2)

let separator = BehaviorSubject<String>(value: "-")

let wordMap = ["0" : "zero", "1" : "one", "2" : "two", "3" : "three", "4" : "four"]

func rollDice(numberOfDice: UInt) -> Observable<String> 
    let maxNumber = wordMap.count
    return Observable.from(0..<numberOfDice)
        .map  _ in Int(arc4random()) % maxNumber 
        .map  String($0) 


let rawPassword = wordCount
    .asObservable()
    .map  wordCount in
        rollDice(numberOfDice: wordCount)
            .map  numbers in wordMap[numbers]! 
    

let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable()) 
    raw, sep in

    raw.reduce("")  prev, value in
        prev == "" ? value : "\(prev)\(sep)\(value)"
    

.switchLatest() // equivalent to: .flatMap  $0 

reducedPassword
    .subscribe(onNext:  print($0) )

separator.onNext("||||")
wordCount.onNext(4)
separator.onNext("___")

输出:

零四 零||||两个 两个||||四个||||零||||一个 三___二___二___四

注意事项

我不确定为什么 wordMap[String : String] 而不仅仅是 [String]。用 wordMap["123"] 而不是 wordMap[123] 来索引地图似乎很奇怪。

如果不使用Observable&lt;Observable&lt;String&gt;&gt;,而是使用Observable&lt;[String]&gt;,会更容易实现和理解。这就是你遇到问题的原因,即使你理解了它,它仍然比它需要的复杂。

记住,键入注释你的变量和闭包,以确保你正在做你所期望的。您会更早发现这些问题。

改进的解决方案

考虑到我上面概述的注释,这是另一种编写方式。

let disposeBag = DisposeBag()

let wordCount = BehaviorSubject<UInt>(value: 2)

let separator = BehaviorSubject<String>(value: "-")

let wordMap = ["zero", "one", "two", "three", "four"]

func rollDice(numberOfDice: UInt) -> [Int] 
    let maxNumber = wordMap.count
    return (0..<numberOfDice).map  _ in
        Int(arc4random()) % maxNumber
    


let words: Observable<[String]> = wordCount
    .asObservable()
    .map  (count: UInt) -> [String] in
        return rollDice(numberOfDice: count)
            .map  roll in wordMap[roll] 
    

let password: Observable<String> = Observable.combineLatest(words, separator.asObservable()) 
    (words: [String], separator: String) -> String in

    words.reduce("")  prev, value in
        prev == "" ? value : "\(prev)\(separator)\(value)"
    


password.subscribe(onNext:  print($0) )

separator.onNext("||||")
wordCount.onNext(4)
separator.onNext("___")

【讨论】:

以上是关于RxSwift:在使用 flatMap 和 reduce 时需要帮助的主要内容,如果未能解决你的问题,请参考以下文章

使用 if/else 语句返回 observable 不适用于 RxSwift flatMap

RXSwift flatMap 两种方法

RxSwift flatMap 最新一次,无需处理

RxSwift: 链 Completable 到 Observable

RxSwift:调用中的额外参数“onError”

地图中的 RxSwift 多个可观察对象