RxSwift 計算器demo

Calculator.gif

這是一個計算器煤惩,是RxSwift官方的示例demo,可在 這里下載

整個項目只有3個文件


image.png

我們先看CalculatorViewController.swift這個文件猾浦,核心代碼如下

override func viewDidLoad() {
        typealias FeedbackLoop = (ObservableSchedulerContext<CalculatorState>) -> Observable<CalculatorCommand>

        let uiFeedback: FeedbackLoop = bind(self) { this, state in
            let subscriptions = [
                state.map { $0.screen }.bind(to: this.resultLabel.rx.text),
                state.map { $0.sign }.bind(to: this.lastSignLabel.rx.text)
            ]

            let events: [Observable<CalculatorCommand>] = [
                    this.allClearButton.rx.tap.map { _ in .clear },

                    this.changeSignButton.rx.tap.map { _ in .changeSign },
                    this.percentButton.rx.tap.map { _ in .percent },

                    this.divideButton.rx.tap.map { _ in .operation(.division) },
                    this.multiplyButton.rx.tap.map { _ in .operation(.multiplication) },
                    this.minusButton.rx.tap.map { _ in .operation(.subtraction) },
                    this.plusButton.rx.tap.map { _ in .operation(.addition) },

                    this.equalButton.rx.tap.map { _ in .equal },

                    this.dotButton.rx.tap.map { _ in  .addDot },

                    this.zeroButton.rx.tap.map { _ in .addNumber("0") },
                    this.oneButton.rx.tap.map { _ in .addNumber("1") },
                    this.twoButton.rx.tap.map { _ in .addNumber("2") },
                    this.threeButton.rx.tap.map { _ in .addNumber("3") },
                    this.fourButton.rx.tap.map { _ in .addNumber("4") },
                    this.fiveButton.rx.tap.map { _ in .addNumber("5") },
                    this.sixButton.rx.tap.map { _ in .addNumber("6") },
                    this.sevenButton.rx.tap.map { _ in .addNumber("7") },
                    this.eightButton.rx.tap.map { _ in .addNumber("8") },
                    this.nineButton.rx.tap.map { _ in .addNumber("9") }
                ]

            return Bindings(subscriptions: subscriptions, events: events)
        }
        
        Observable.system(
            initialState: CalculatorState.initial,
            reduce: CalculatorState.reduce,
            scheduler: MainScheduler.instance,
            scheduledFeedback: uiFeedback
        )
            .subscribe()
            .disposed(by: disposeBag)
    }

    func formatResult(_ result: String) -> String {
        if result.hasSuffix(".0") {
            return String(result[result.startIndex ..< result.index(result.endIndex, offsetBy: -2)])
        } else {
            return result
        }
    }

該項目使用了RxFeedback框架陆错,有時間我再寫一篇分析RxFeedback源碼的文章。 關于RxFeedback的簡介 看這里

bind(self) { this, state in  ...

這里閉包里的this即是bind函數(shù)傳入的 self

上面的代碼段 let events: [Observable<CalculatorCommand>] = [ ... ]
表示將按鈕的點擊事件轉換為相應的命令跃巡。

 let subscriptions = [
                state.map { $0.screen }.bind(to: this.resultLabel.rx.text),
                state.map { $0.sign }.bind(to: this.lastSignLabel.rx.text)
            ]

上面的代碼表示將state映射到計算符和屏顯危号。


image.png

這個計算器主要有三種狀態(tài):

enum CalculatorState {
    case oneOperand(screen: String)
    case oneOperandAndOperator(operand: Double, operator: Operator)
    case twoOperandsAndOperator(operand: Double, operator: Operator, screen: String)
}

oneOperand 一個操作數(shù),例如: 輸入 1 時的狀態(tài)
oneOperandAndOperator 一個操作數(shù)和一個運算符素邪,例如: 輸入 1 + 時的狀態(tài)
twoOperandsAndOperator 兩個操作數(shù)和一個運算符外莲,例如: 輸入 1 + 2 時的狀態(tài)

計算器提供了七種命令

enum CalculatorCommand {
    case clear
    case changeSign
    case percent
    case operation(Operator)
    case equal
    case addNumber(Character)
    case addDot
}
extension CalculatorState {
    static func reduce(state: CalculatorState, _ x: CalculatorCommand) -> CalculatorState {
        switch x {
        case .clear:
            return CalculatorState.initial
        case .addNumber(let c):
            return state.mapScreen { return $0 == "0" ? String(c) : $0 + 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)
            case let .twoOperandsAndOperator(operand, oldOperator, screen):
                return .twoOperandsAndOperator(operand: oldOperator.perform(operand, screen.doubleValue), operator: o, screen: "0")
            }
        case .equal:
            switch state {
            case let .twoOperandsAndOperator(operand, operat, screen):
                let result = operat.perform(operand, screen.doubleValue)
                return .oneOperand(screen: String(result))
            default:
                return state
            }
        }
    }
}

一些其他輔助代碼:

extension CalculatorState {
    static let initial = CalculatorState.oneOperand(screen: "0")

    func mapScreen(transform: (String) -> String) -> CalculatorState {
        switch self {
        case let .oneOperand(screen):
            return .oneOperand(screen: transform(screen))
        case let .oneOperandAndOperator(operand, operat):
            return .twoOperandsAndOperator(operand: operand, operator: operat, screen: transform("0"))
        case let .twoOperandsAndOperator(operand, operat, screen):
            return .twoOperandsAndOperator(operand: operand, operator: operat, screen: transform(screen))
        }
    }

    var screen: String {
        switch self {
        case let .oneOperand(screen):
            return screen
        case .oneOperandAndOperator:
            return "0"
        case let .twoOperandsAndOperator(_, _, screen):
            return screen
        }
    }

    var sign: String {
        switch self {
        case .oneOperand:
            return ""
        case let .oneOperandAndOperator(_, o):
            return o.sign
        case let .twoOperandsAndOperator(_, o, _):
            return o.sign
        }
    }
}
extension Operator {
    var sign: String {
        switch self {
        case .addition:         return "+"
        case .subtraction:      return "-"
        case .multiplication:   return "×"
        case .division:         return "/"
        }
    }
    
    var perform: (Double, Double) -> Double {
        switch self {
        case .addition:         return (+)
        case .subtraction:      return (-)
        case .multiplication:   return (*)
        case .division:         return (/)
        }
    }
/*
這段代碼解釋一下,這種寫法我也是第一次見兔朦。對于addition偷线,它返回了一個接受兩個Double值并返回它們之和的閉包,即(+)沽甥。
對于subtraction声邦,它返回了一個接受兩個Double值并返回它們之差的閉包,即(-)摆舟。
對于multiplication亥曹,它返回了一個接受兩個Double值并返回它們乘積的閉包邓了,即(*)。
對于division媳瞪,它返回了一個接受兩個Double值并返回它們商的閉包骗炉,即(/)。
*/

}

private extension String {
    var doubleValue: Double {
        guard let double = Double(self) else {
           return Double.infinity
        }
        return double
    }
}

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蛇受,一起剝皮案震驚了整個濱河市句葵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兢仰,老刑警劉巖乍丈,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異把将,居然都是意外死亡轻专,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門秸弛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铭若,“玉大人,你說我怎么就攤上這事递览。” “怎么了瞳腌?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵绞铃,是天一觀的道長。 經(jīng)常有香客問我嫂侍,道長儿捧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任挑宠,我火速辦了婚禮菲盾,結果婚禮上,老公的妹妹穿的比我還像新娘各淀。我一直安慰自己懒鉴,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布碎浇。 她就那樣靜靜地躺著临谱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奴璃。 梳的紋絲不亂的頭發(fā)上悉默,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音苟穆,去河邊找鬼抄课。 笑死唱星,一個胖子當著我的面吹牛,可吹牛的內容都是我干的跟磨。 我是一名探鬼主播魏颓,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吱晒!你這毒婦竟也來了甸饱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤仑濒,失蹤者是張志新(化名)和其女友劉穎叹话,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墩瞳,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡驼壶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喉酌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片热凹。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泪电,靈堂內的尸體忽然破棺而出般妙,到底是詐尸還是另有隱情,我是刑警寧澤相速,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布碟渺,位于F島的核電站,受9級特大地震影響突诬,放射性物質發(fā)生泄漏苫拍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一旺隙、第九天 我趴在偏房一處隱蔽的房頂上張望绒极。 院中可真熱鬧,春花似錦蔬捷、人聲如沸垄提。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塔淤。三九已至,卻和暖如春高蜂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背罕容。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工备恤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稿饰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓露泊,卻偏偏與公主長得像喉镰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惭笑,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容