【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