我正在编写一个简单的Diceware密码生成器来试验RxSwift。
我正在努力在不同的步骤中使用flatMap
和reduce
。
当前代码
我有一个可观察的wordCount
与UIStepper
值绑定,并生成一个包含给定数量字词的新密码。
let rawPassword = wordCount
.asObservable()
.map { wordCount in
self.rollDice(numberOfDice: wordCount)
.map { numbers in wordMap[numbers]! }
}
rollDice
会返回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更改单词分隔符。我只想在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
是正确的方法吗?
答案 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 }
我把所有代码和修复程序放在一起,可以按原样运行,没有用户界面:
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("___")