单数据流的VC设计

背景


对Swift的函数式编程一直都只停留在《函数式Swift》案例中,长久以来命令式编程思想也让我很难真正的理解相应内容,更没有勇气在项目运用函数式编程相关的内容。但是最近阅读了王巍大神的单向数据流动的函数式 View Controller,通过对文章内容的理解和实践,让我对Swift函数式编程思想有深刻的理解,也切实体会到它的好处。为了加深记忆和方便理解,对整个理解和实践的过程进行梳理。

1. 函数式 View Controller设计的原理。

2. POCT项目Mine页面的重设计。

3. 函数式 View Controller设计的利弊权衡。

函数式 View Controller设计的原理

函数式的核心就是输入的值确定,那输出的结果是唯一的。

关于函数式View Controller的设计,可以理解为用户**操作行为**改变VC**状态**,订阅VC的状态变化,通过变化内容实现UI更新。单个行为对应唯一的页面显示。

所以vc显示最基本的流程图应该如图1.1所示:


如果是没有复杂交互逻辑的页面,比如单行为,单显示结果的页面这样设计结构也可以。但是一旦遇到复杂逻辑的页面,显然无法满足。对于一般页面而言,用户行为->VC显示的逻辑按照以下3个步骤进行。

1. 用户行为(网络请求或者点击事件)。

2. 判断之前VC的显示内容

3. 再根据相应的内容变化,对具体的变化内容进行VC的UI更新。

现阶段大多数的vc实现类似于下面的流程进行设计:


这么实践从逻辑上而言没有任何问题,但是根本没有关于用户行为的管理,也就是“用户行为”这一层是很乱,随着页面的逻辑越来越复杂,用户的行为越来越多,导致“数据判断”部分的内容会杂乱的散落在vc层各个角落,这对于vc的维护者或者接盘手而言简直是灾难,可能还不如重新设计来的划算。这工作时间浪费得毫无意义。

幸好函数式的VC设计,给我们提供了新的实践思路,大致可以将其流程理解为:


这个流程跟之前的比,还是有很大的改进,将网络所有用户的本地操作以改变的vc状态进行统一管理,但是针对于网络请求的行为这一行为,完全和用户操作和vc的状态剥离开,但事实上,vc显示的主要操作源还是网络请求,将如此大的模块从vc设计剥离开,对代码的质量和可读性的提升并不大。所以为了使vc设计的架构更加饱满,需要对结构进行再设计。


注: 整个流程估计比较难理解的就是“逻辑判断”再返回到“用户操作”的部分。如果是产生了网络指令,那么会在逻辑判断的阶段进行网络请求,通过请求的数据,重新引起用户操作,再改变vc状态。核心还是把所有的状态内容都进行统一的存储。

终引起UI发生变化的因子只有vc状态发生改变。通过这样的设计可以清晰梳理vc的业务逻辑,设计VC的阶段也是对交互逻辑梳理的阶段,可以从vc设计的角度了解功能,这样一来可以保证对开发的代码负责,同时提高可读性和可维护性。即便遇到“事而多”的产品,提出各种各样异想天开的行为,只要想得到,保证完成起来轻松愉快,一口气实现增加10个功能,不费劲儿~关于函数式vc结合对王巍大神的文章和自己的理解,总结出的核心内容只有三点:单操作行为对应唯一的UI显示。单操作行为对应唯一的UI显示。单操作行为对应唯一的UI显示。


现阶段根据我的理解,上述的函数式vc设计流程是比较完整的方案,如果在之后有更好的方案,会进行更新。

POCT项目Mine页面的重设计

Mine页面核心业务,一些基本操作的入口、通过某些状态的改变对某些入口进行提示。在之前的设计中已经对Mine页面所涉及到的所有数据内容进行封装,所以对其进行重新设计的过程中只需要关注业务即可。

根据图1.4的流程,设计函数式Mine页面。可以把网络请求和用户操作放在统一的Action模块进行管理,而把产生的网络指令和vc状态放在State模块进行管理,而在逻辑判断阶段只需要区分State改变的是vc状态还是网络指令即可,最后进行相关的UI更新即可。

具体实现

首先是对Mine页面的***业务梳理*:

**进行梳理,其主要的业务如下所示:

1. 关于系统消息和推送消息红点提醒。

2. 关于七鱼消息的推送的红点提醒。

3. 设备连接的提示。

4. 用户处于怀孕状态还是备孕状态的提示。

5. 用户名修改的显示。

6. 如果处于检测值已经是怀孕了,但是状态还处于备孕状态的,有一个tips的提示用户切换状态。

7. 网络请求。

针对Mine页面涉及的以上七种业务,在Action模块中需要管理这七种Action:

enum PersonCenterAction: ActionType {

        // with cell

        case messageBadge(isShowBadge: Bool, messageType: BadgeType)

        case serviceBadge(isShowBadge: Bool, count: Int)

        case deviceBadge(isShowBadge: Bool)



        // with headview

        case headTip(isShowTip: Bool)

        case stateButton(stateTitle: String)

        case setTitle(title: String)

        // command

        case loadData

}


针对于每一种Action,都会改变vc的State,所以需要对每一个Action所引发的State改变进行进行操作:

fileprivate lazy var reducer: (PersonCenterState, PersonCenterAction) -> (state: PersonCenterState, command: PersonCenterCommand?) = { (state: PersonCenterState, action: PersonCenterAction) in

        var personState = state

        var command: PersonCenterCommand? = nil

        switch action {

                case .messageBadge(let isShowBadge, let msssageType):                     

                        personState.messageState[msssageType] = isShowBadge         

                        personState.staticData[1][0].isRed = false

                        for s in personState.messageState {

                                if s.value {

                                        personState.staticData[1][0].isRed = true

                                }

                        }

              case .serviceBadge(let isShowBadge, let count):

                    personState.staticData[2][1].isRed = isShowBadge

                    personState.staticData[2][1].unreadCount = count

            case .deviceBadge(let isShowBadge):

                    personState.staticData[0][0].isShowTips = [false, false, isShowBadge]

            case .headTip(let isShowTip):

                    personState.headData?.didShownBubbles = isShowTip

            case .stateButton(let stateString):

                    personState.headData?.stateButtonTitle = stateString

            case .setTitle(let title):

                    personState.headData?.title = title

            case .loadData:

                    command = PersonCenterCommand.getFemaleData

        }

        return (personState, command)

}

在State发生改变之后,需要针对每一种情况进行分开处理,这里主要处理工作就是tableview的刷新和header内容的更新。

func stateDidChange(_ state: PersonCenterState, previousState: PersonCenterState?, command: PersonCenterCommand?) {

        // tableview的状态变化 相对而言会增加内存消耗

        guard let `command` = command else {

            headerView.model = state.headData

            tableView.reloadData()

            return

        }

        // 关于消息的数据刷新

        switch command {

            case .getFemaleData: loadFemaleInfo()

    } }

以上就是函数式Mine VC的核心代码。

函数式 View Controller设计的利弊权衡

存在的问题分析:

在stateDidChange方法中关于tableView.reloadData()的使用可能会有疑问,明明大多数行为只是修改了某一行的内容,而到了这里,一股脑全刷新了。当然可以细化将每次的修改针对具体的某个Cell进行刷新方案也很多:

1. 通过比对之前的State和之后的State.

2. 在State中增加一个关于修改了哪一个Cell或者header的属性,使每一个修改都有特定的标记.

首先来谈谈第一点,确定要比对State中的某一个属性的变化,会增加很多的逻辑操作,不建议使用;关于第二点,它确实可以使方法能够正常调用,但是问题就是这是一个完全和业务逻辑都无关的操作,作为State的某个属性耦合性太强。因此在方法中还是使用tableView.reloadData()。但是总感觉这个明显可以提升性能的地方犹如一根针插到心里。

对tableView.reloadData() 和 tableView.reloadRows从耗时和CPU消耗两个方面性能进行比对:


根据测试reloadData()和reloadRows()相比在性能上更有优势,所以使用reloadData()不用担心性能问题,只是跟reloadRows()相比在表意上会有所欠缺。

测试相关

函数式的VC设计有利于编写UnitTest,虽然现在我们工程的量级与QA的配备不需要开发自行编写UnitTest,但是如果可以这对于开发代码的质量提升还是有推进作用的。可以“干掉”天天给我们提Bug的QA,多开心~~~(/:X-)/:X-)


现阶段对函数式思考的内容还比较单薄,还需要再更多的实践中运用起来,才能真正的理解函数式。如有大神能给一些指导性的建议那是极好的。


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

推荐阅读更多精彩内容