【iOS】RxSwift官方Example5–計算器【轉】
- 2020 年 3 月 31 日
- 筆記
原文地址
https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/more_demo/calculator.html
前言
本來這一篇是想自己寫的,但是看完這個例子後,一臉懵逼,只好去搜搜有沒有人分析這篇例子。結果還真給我搜索到了,看完後,發現這篇Podcast寫的非常詳細,推薦學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