RxSwift:使用flatMap和reduce需要帮助

时间:2016-08-24 09:36:59

标签: ios swift rx-swift

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

当前代码

我有一个可观察的wordCountUIStepper值绑定,并生成一个包含给定数量字词的新密码。

enter image description here

    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 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更改单词分隔符。我只想在rawPassword的文字更新后重新应用UITextField上的缩减。

我正在尝试使用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是正确的方法吗?

1 个答案:

答案 0 :(得分:2)

问题

问题是reducedPassword(在combineLatest案例中)确实是Observable<Observable<String>>。它有助于输入注释,特别是在学习RxSwift时,以确保事物实际上符合您的期望。

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

修复

因此,要让修改后的reducedPassword生效,您需要获取通过(Observable<String>)的元素并将其替换为当前Observable<Observable<String>>。因此,请使用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 }

All Together

我把所有代码和修复程序放在一起,可以按原样运行,没有用户界面:

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("___")

输出:

  

零个
  零个|||| 2个
  2 |||| |||| 4 ||||零一个
  三个___两个_two___四个

备注

  • 我不确定为什么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($0) })

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