【iOS】RxSwift官方Example5–计算器【转】
- 2020 年 3 月 31 日
- 筆記
原文地址
https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/more_demo/calculator.html
前言
本来这一篇是想自己写的,但是看完这个例子后,一脸懵逼,只好去搜搜有没有人分析这篇例子。结果还真给我搜索到了,看完后,发现这篇播客写的非常详细,推荐学Rxswift的都去看看。
简介
还是先来直接看演示的例子吧。

计算器
功能就不介绍了。这个的计算器是RxFeedback架构,实际上,这个RxFeedback架构,我看的还是云里雾里的,还是无法理解。
整体分析
整体结构
图来自转载出,侵删

整体结构
从上图可以看到,我们点击的按钮,会先合成命令,然后根据输入的命令,决定了计算器的状态,最后根据计算器的状态,做出对应的操作,也就是上图的“计算符”和“屏显”
合成命令
显然,我们的命令是通过点击按钮产生的,由于这里有许多按钮,因此我们需要借助Observable.merge方法。
let commands: Observable<CalculatorCommand> = Observable.merge([ allClearButton.rx.tap.map { _ in .clear}, changeSignButton.rx.tap.map { _ in .changeSign}, percentButton.rx.tap.map { _ in .percent}, divideButton.rx.tap.map { _ in .operation(.division)}, multiplyButton.rx.tap.map { _ in .operation(.multiplication)}, minusButton.rx.tap.map { _ in .operation(.substraction)}, plusButton.rx.tap.map { _ in .operation(.addition)}, equalButton.rx.tap.map { _ in .equal}, dotButton.rx.tap.map { _ in .addDot}, zeroButton.rx.tap.map { _ in .addNumber("0")}, oneButton.rx.tap.map { _ in .addNumber("1")}, twoButton.rx.tap.map { _ in .addNumber("2")}, threeButton.rx.tap.map { _ in .addNumber("3")}, fourButton.rx.tap.map { _ in .addNumber("4")}, fiveButton.rx.tap.map { _ in .addNumber("5")}, sixButton.rx.tap.map { _ in .addNumber("6")}, sevenButton.rx.tap.map { _ in .addNumber("7")}, eightButton.rx.tap.map { _ in .addNumber("8")}, nineButton.rx.tap.map { _ in .addNumber("9")} ])
通过使用 map 方法将按钮点击事件转换为对应的命令。如: 将 allClearButton 点击事件转换为清除命令,将 plusButton 点击事件转换为相加命令,将 oneButton 点击事件转换为添加数字1命令。最后使用 merge 操作符将这些命令合并。于是就得到了我们所需要的命令序列。
命令 -> 状态之间的转换
几乎每个页面都是有状态的。我们通过命令序列来对状态进行修改,然后产生一个新的状态。例如,刚进页面后,点击了按钮 1 。那么初始状态为 0,在执行添加数字1命令后,状态就更新为 1。通过这种变换方式,就可以生成一个状态序列:

命令 -> 状态之间的转换
let system = Observable.system( CalculatorState.initial, accumulator: CalculatorState.reduce, scheduler: MainScheduler.instance, feedback: { _ in commands } ) .debug("calculator state") .shareReplayLatestWhileConnected()
根据状态显示
由命令序列触发,对页面状态进行更新,在用更新后的状态组成一个序列。这就是我们所需要的状态序列。接下来我们用这个状态序列来控制页面显示

根据状态显示
system.map { $0.screen } .bind(to: resultLabel.rx.text) .addDisposableTo(disposeBag) system.map { $0.sign } .bind(to: lastSignLabel.rx.text) .addDisposableTo(disposeBag)
用 state.screen
来控制 resultLabel
的显示内容。用 state.sign 来控制 lastSignLabel 的显示内容。
Calculator
控制器主要负责数据绑定,而整个计算器的大脑在 Calculator.swift
文件内。
State:
这个页面主要有三种状态:
enum CalculatorState { case oneOperand(screen: String) case oneOperandAndOperator(operand: Double, operator: Operator) case twoOperandAndOperator(operand: Double, operator: Operator, screen: String) }
- oneOperand 一个操作数,例如:进入页面后,输入 1 时的状态
- oneOperandAndOperator 一个操作数和一个运算符,例如:进入页面后,输入 1 + 时的状态
- twoOperandsAndOperator 两个操作数和一个运算符,例如:进入页面后,输入 1 + 2 时的状态
Command:
一共有7个指令:
enum Operator { case addition case subtraction case multiplication case division } enum CalculatorCommand { case clear case changeSign case percent case operation(Operator) case equal case addNumber(Character) case addDot }
- clear 清除,重置
- changeSign 改变正负号
- percent 百分比
- operation 四则运算
- equal 等于
- addNumber 输入数字
- addDot 输入 “.”
reduce
当命令产生时,将它应用到当前状态上,然后生成新的状态:

输入命令后的状态转换
extension CalculatorState { static func reduce(state: CalculatorState, _ x: CalculatorCommand) -> CalculatorState { switch x { case .clear: return CalculatorState.initial case .addNumber(let c): return state.mapScreen(transform: { (str) -> String in return str == "0" ? String(c) : str + String(c) }) case .addDot: return state.mapScreen { $0.range(of: ".") == nil ? $0 + "." : $0 } case .changeSign: return state.mapScreen { "(-(Double($0) ?? 0.0))" } case .percent: return state.mapScreen { "((Double($0) ?? 0.0) / 100.0)" } case .operation(let o): switch state { case let .oneOperand(screen): // 如果只有一个操作数,就添加操作符 return .oneOperandAndOperator(operand: screen.doubleValue, operator: o) // 如果有一个操作数和操作符,就替换操作符 case let .oneOperandAndOperator(operand, _): return .oneOperandAndOperator(operand: operand, operator: o) // 如果有两个操作数和一个操作符,将他们的计算结果作为操作数保留,然后加入新的操作符,以及一个操作数 0. case let .twoOperandAndOperator(operand, oldOperator, screen): return .twoOperandAndOperator(operand: oldOperator.perform(operand, screen.doubleValue), operator: o, screen: "0") } case .equal: switch state { //如果当前有两个操作数和一个操作符,将他们的计算结果作为操作数保留。否则什么都不做。 case let .twoOperandAndOperator(operand, opeart, screen): let result = opeart.perform(operand, screen.doubleValue) return .oneOperand(screen: String(result)) default: return state } } } }
- clear 重置当前状态
- addNumber, addDot, changeSign, percent 只需要更改屏显即可
- operation 需要根据当前状态来确定如何变化状态。
- 如果只有一个操作数,就添加操作符。
- 如果有一个操作数和操作符,就替换操作符。
- 如果有两个操作数和一个操作符,将他们的计算结果作为操作数保留,然后加入新的操作符,以及一个操作数 0.
- equal 如果当前有两个操作数和一个操作符,将他们的计算结果作为操作数保留。否则什么都不做。
总结
这篇的核心架构是RxFeedback,反正我是不太能理解,不打算深入了解了。
Demo地址
https://github.com/maple1994/RxSwfitTest