原文地址
https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/more_demo/calculator.html
前言
本來這一篇是想自己寫的,但是看完這個例子后,一臉懵逼,只好去搜搜有沒有人分析這篇例子。結(jié)果還真給我搜索到了,看完后,發(fā)現(xiàn)這篇播客寫的非常詳細(xì),推薦學(xué)Rxswift的都去看看。
簡介
還是先來直接看演示的例子吧。
功能就不介紹了。這個的計(jì)算器是RxFeedback架構(gòu),實(shí)際上,這個RxFeedback架構(gòu),我看的還是云里霧里的,還是無法理解。
整體分析
整體結(jié)構(gòu)
圖來自轉(zhuǎn)載出,侵刪
從上圖可以看到,我們點(diǎn)擊的按鈕,會先合成命令,然后根據(jù)輸入的命令,決定了計(jì)算器的狀態(tài),最后根據(jù)計(jì)算器的狀態(tài),做出對應(yīng)的操作,也就是上圖的“計(jì)算符”和“屏顯”
合成命令
顯然,我們的命令是通過點(diǎn)擊按鈕產(chǎn)生的,由于這里有許多按鈕,因此我們需要借助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 方法將按鈕點(diǎn)擊事件轉(zhuǎn)換為對應(yīng)的命令。如:
將 allClearButton
點(diǎn)擊事件轉(zhuǎn)換為清除命令,將 plusButton
點(diǎn)擊事件轉(zhuǎn)換為相加命令,將 oneButton
點(diǎn)擊事件轉(zhuǎn)換為添加數(shù)字1命令。最后使用 merge 操作符將這些命令合并。于是就得到了我們所需要的命令序列。
命令 -> 狀態(tài)之間的轉(zhuǎn)換
幾乎每個頁面都是有狀態(tài)的。我們通過命令序列來對狀態(tài)進(jìn)行修改,然后產(chǎn)生一個新的狀態(tài)。例如,剛進(jìn)頁面后,點(diǎn)擊了按鈕 1 。那么初始狀態(tài)為 0,在執(zhí)行添加數(shù)字1命令后,狀態(tài)就更新為 1。通過這種變換方式,就可以生成一個狀態(tài)序列:
let system = Observable.system(
CalculatorState.initial,
accumulator: CalculatorState.reduce,
scheduler: MainScheduler.instance,
feedback: { _ in commands }
)
.debug("calculator state")
.shareReplayLatestWhileConnected()
根據(jù)狀態(tài)顯示
由命令序列觸發(fā),對頁面狀態(tài)進(jìn)行更新,在用更新后的狀態(tài)組成一個序列。這就是我們所需要的狀態(tài)序列。接下來我們用這個狀態(tài)序列來控制頁面顯示
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
的顯示內(nèi)容。用 state.sign 來控制 lastSignLabel 的顯示內(nèi)容。
Calculator
控制器主要負(fù)責(zé)數(shù)據(jù)綁定,而整個計(jì)算器的大腦在 Calculator.swift
文件內(nèi)。
State:
這個頁面主要有三種狀態(tài):
enum CalculatorState {
case oneOperand(screen: String)
case oneOperandAndOperator(operand: Double, operator: Operator)
case twoOperandAndOperator(operand: Double, operator: Operator, screen: String)
}
- oneOperand 一個操作數(shù),例如:進(jìn)入頁面后,輸入 1 時的狀態(tài)
- oneOperandAndOperator 一個操作數(shù)和一個運(yùn)算符,例如:進(jìn)入頁面后,輸入 1 + 時的狀態(tài)
- twoOperandsAndOperator 兩個操作數(shù)和一個運(yùn)算符,例如:進(jìn)入頁面后,輸入 1 + 2 時的狀態(tài)
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 改變正負(fù)號
- percent 百分比
- operation 四則運(yùn)算
- equal 等于
- addNumber 輸入數(shù)字
- addDot 輸入 “.”
reduce
當(dāng)命令產(chǎn)生時,將它應(yīng)用到當(dāng)前狀態(tài)上,然后生成新的狀態(tài):
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):
// 如果只有一個操作數(shù),就添加操作符
return .oneOperandAndOperator(operand: screen.doubleValue, operator: o)
// 如果有一個操作數(shù)和操作符,就替換操作符
case let .oneOperandAndOperator(operand, _):
return .oneOperandAndOperator(operand: operand, operator: o)
// 如果有兩個操作數(shù)和一個操作符,將他們的計(jì)算結(jié)果作為操作數(shù)保留,然后加入新的操作符,以及一個操作數(shù) 0.
case let .twoOperandAndOperator(operand, oldOperator, screen):
return .twoOperandAndOperator(operand: oldOperator.perform(operand, screen.doubleValue), operator: o, screen: "0")
}
case .equal:
switch state {
//如果當(dāng)前有兩個操作數(shù)和一個操作符,將他們的計(jì)算結(jié)果作為操作數(shù)保留。否則什么都不做。
case let .twoOperandAndOperator(operand, opeart, screen):
let result = opeart.perform(operand, screen.doubleValue)
return .oneOperand(screen: String(result))
default:
return state
}
}
}
}
- clear 重置當(dāng)前狀態(tài)
- addNumber, addDot, changeSign, percent 只需要更改屏顯即可
- operation 需要根據(jù)當(dāng)前狀態(tài)來確定如何變化狀態(tài)。
- 如果只有一個操作數(shù),就添加操作符。
- 如果有一個操作數(shù)和操作符,就替換操作符。
- 如果有兩個操作數(shù)和一個操作符,將他們的計(jì)算結(jié)果作為操作數(shù)保留,然后加入新的操作符,以及一個操作數(shù) 0.
- equal 如果當(dāng)前有兩個操作數(shù)和一個操作符,將他們的計(jì)算結(jié)果作為操作數(shù)保留。否則什么都不做。
總結(jié)
這篇的核心架構(gòu)是RxFeedback,反正我是不太能理解,不打算深入了解了。