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

原文地址

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

前言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • Ubuntu的发音 Ubuntu,源于非洲祖鲁人和科萨人的语言,发作 oo-boon-too 的音。了解发音是有意...
    萤火虫de梦阅读 99,130评论 9 467
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • 我是一个爱看小说的人,这样子媒体人的文章我还是第一次看。感觉脑容量不够用。看到文章讲到医生这个章节时,内心感触还是...
    小草的常阅书院阅读 357评论 0 2