【iOS】RxSwift官方Example5--计算器【转】

原文地址

https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/more_demo/calculator.html

前言

本来这一篇是想自己写的,但是看完这个例子后,一脸懵逼,只好去搜搜有没有人分析这篇例子。结果还真给我搜索到了,看完后,发现这篇播客写的非常详细,推荐学Rxswift的都去看看。

简介

还是先来直接看演示的例子吧。

计算器

功能就不介绍了。这个的计算器是RxFeedback架构,实际上,这个RxFeedback架构,我看的还是云里雾里的,还是无法理解。

整体分析

整体结构

图来自转载出,侵删

整体结构

从上图可以看到,我们点击的按钮,会先合成命令,然后根据输入的命令,决定了计算器的状态,最后根据计算器的状态,做出对应的操作,也就是上图的“计算符”和“屏显”

合成命令

显然,我们的命令是通过点击按钮产生的,由于这里有许多按钮,因此我们需要借助Observable.merge方法。

代码语言:javascript
复制
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(&#34;0&#34;)},
        oneButton.rx.tap.map { _ in .addNumber(&#34;1&#34;)},
        twoButton.rx.tap.map { _ in .addNumber(&#34;2&#34;)},
        threeButton.rx.tap.map { _ in .addNumber(&#34;3&#34;)},
        fourButton.rx.tap.map { _ in .addNumber(&#34;4&#34;)},
        fiveButton.rx.tap.map { _ in .addNumber(&#34;5&#34;)},
        sixButton.rx.tap.map { _ in .addNumber(&#34;6&#34;)},
        sevenButton.rx.tap.map { _ in .addNumber(&#34;7&#34;)},
        eightButton.rx.tap.map { _ in .addNumber(&#34;8&#34;)},
        nineButton.rx.tap.map { _ in .addNumber(&#34;9&#34;)}
    ])</code></pre></div></div><p>通过使用 map 方法将按钮点击事件转换为对应的命令。如:

将 allClearButton
点击事件转换为清除命令,将 plusButton
点击事件转换为相加命令,将 oneButton
点击事件转换为添加数字1命令。最后使用 merge 操作符将这些命令合并。于是就得到了我们所需要的命令序列

命令 -> 状态之间的转换

几乎每个页面都是有状态的。我们通过命令序列来对状态进行修改,然后产生一个新的状态。例如,刚进页面后,点击了按钮 1 。那么初始状态为 0,在执行添加数字1命令后,状态就更新为 1。通过这种变换方式,就可以生成一个状态序列:

命令 -> 状态之间的转换

代码语言:javascript
复制
let system = Observable.system(
CalculatorState.initial,
accumulator: CalculatorState.reduce,
scheduler: MainScheduler.instance,
feedback: { _ in commands }
)
.debug("calculator state")
.shareReplayLatestWhileConnected()

根据状态显示

由命令序列触发,对页面状态进行更新,在用更新后的状态组成一个序列。这就是我们所需要的状态序列。接下来我们用这个状态序列来控制页面显示

根据状态显示

代码语言:javascript
复制
system.map { $0.screen }
.bind(to: resultLabel.rx.text)
.addDisposableTo(disposeBag)

    system.map { $0.sign }
    .bind(to: lastSignLabel.rx.text)
    .addDisposableTo(disposeBag)</code></pre></div></div><p>用 <code>state.screen</code> 来控制 <code>resultLabel</code>的显示内容。用 state.sign 来控制 lastSignLabel 的显示内容。</p><h2 id="2oa13" name="Calculator">Calculator</h2><p>控制器主要负责数据绑定,而整个计算器的大脑在 <code>Calculator.swift</code> 文件内。</p><h3 id="b31o2" name="State:">State:</h3><p>这个页面主要有三种状态:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">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个指令:

代码语言:javascript
复制
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

当命令产生时,将它应用到当前状态上,然后生成新的状态:

输入命令后的状态转换

代码语言:javascript
复制
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