RxSwift:需要帮助使用 flatMap 和 reduce
RxSwift: Need help using flatMap and reduce
我正在编写一个简单的 Diceware 密码生成器来试验 RxSwift。
我正在努力在不同的步骤中使用 flatMap
和 reduce
。
当前代码
我有一个绑定到 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
其中 flatMap
和 reduce
到 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
是正确的方法吗?
问题
问题是 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("___")
我正在编写一个简单的 Diceware 密码生成器来试验 RxSwift。
我正在努力在不同的步骤中使用 flatMap
和 reduce
。
当前代码
我有一个绑定到 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
其中 flatMap
和 reduce
到 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
是正确的方法吗?
问题
问题是 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("___")