RxSwift:需要帮助使用 flatMap 和 reduce

RxSwift: Need help using flatMap and reduce

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

当前代码

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

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

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

rawPassword 是一个 Observable<Observable<String>>

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

然后我有一个 reducedPassword 其中 flatMapreduceString:

    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 是正确的方法吗?

问题

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

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

修复

因此,要使修改后的 reducedPassword 生效,您需要获取通过 (Observable<String>) 的元素并用它替换当前的 Observable<Observable<String>>。所以使用 switchLatest() (这就像做 flatMap { [=22=] }):

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

    raw.reduce("") { prev, value in
        prev == "" ? value : "\(prev)\(sep)\(value)"
    }
}
.switchLatest() // equivalent to: .flatMap { [=11=] }

齐心协力

我将你所有的代码连同修复程序放在一起,可以 运行 原样,没有 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([=12=]) }
}

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 { [=12=] }

reducedPassword
    .subscribe(onNext: { print([=12=]) })

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

输出:

zero-four
zero||||two
two||||four||||zero||||one
three___two___two___four

备注

  • 我不确定为什么 wordMap[String : String] 而不是 [String]。用 wordMap["123"] 而不是 wordMap[123].

  • 来索引地图似乎很奇怪
  • 如果您不使用 Observable<Observable<String>>,而是使用 Observable<[String]>,将会更容易实施和理解。这就是您遇到问题的原因,即使您理解了它,它仍然比需要的更复杂。

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

改进的解决方案

考虑到我在上面概述的注释,您可以采用另一种方式编写它。

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([=13=]) })

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