移动客户端架构案例分析与思考

移动客户端架构案例分析与思考

写在前面

关于题目

分享之前,想说一下为什么选择了“架构”这个主题,其实初衷有两个:

第一,“架构”对于我们来说实在是太重要了,咱们虽然没有架构师这个职位,但是在开发的时候,都需要先有个很好的设计,希望我们的代码是易维护的,而“设计”往往都会落到“架构”上。所以希望这次分享能够对于大家在架构设计上有一点帮助。

第二,即便“架构”如此的重要,大家再聊到“架构”这个话题的时候,还是感觉到有点“虚”。想想原因,可能是因为每个人对于“架构”的理解可能都不太一样,一个人在不同阶段对于“架构”的理解也会不一样,架构设计还很依赖于实践和经验,很多设计细节(取舍)都是在实践中不停的迭代、改进,进而反思才能得到价值观的升级。所以我借这次机会,也将我自己之前零散的架构方面的理解,总结一下,争取能形成一点体系上的认识。希望大家聊起架构时,能稍微不那么“虚”。

分享形式

为了不那么枯燥,今天分享形式,我选了3个架构的案例,来进行分析,来试图讲清楚我对于架构的认识,以及怎么样设计出一个好的架构,当然这个话题太大了,我今天先给大家开个头,希望先能有一点感觉就好。

最后希望大家带着批判的精神来听,欢迎多交流。

第一个案例:猎户语音iOS SDK —— 架构是被业务驱动的

第一个案例,举一个我们自己的,放在第一个来讲,也是想强调“懂业务”对于设计出好的架构来说,是非常重要的,这也是一些人往往会忽略的点。

很多同学肯定想,在项目的一开始,就设计一个完美的架构,以后能 hold 住各种变化。其实这是不可能的,架构一定是随着业务的变化,而不停的演化和进化的,在有的阶段还可能对于之前架构做比较大的调整。

背景

大家都知道,我们同时维护了几个App,比如小豹、小雅、锤子等,这些上层App,都要依赖于底层的一些framework,比如

  • 业务中心 OrionServiceSDK.framework (包括很多主要业务)
  • 信息流 OVSChat.framework
  • 技能商店 OVSSkillStore.framework
  • 声纹 OVSVoicePrint.framework
  • 等等

在早些的时候,我们对于 SDK 的拆分粒度比较细,比如一个“找手机”技能,都会做成一个 framework。当时的 framework,应该在20个左右,各个 framework(组件) 的依赖关系图如下:

历史原因

我们这么做当时的原因很简单,希望能够解耦各个组件,将组件尽量拆细,然后App方想使用什么功能都可以做到热插拔,不多也不少,多好~

理想 VS 现实

理想和现实往往会出现矛盾,到实际应用的时候,这样的设计就给我们带来了问题。如果对每个 framework 进行编号,比如1到20,那么我们理想中是这样的:

但是现实其实是这样的:

对比两张图的含义就是,实现中我们各个App,使用的 framework 大体相同,我们即便把业务拆的很细,若干个被拆分的 framework 其实还总是绑在一起使用。并且,还给我们维护带来了巨大的问题,光每次打Git tag,都要折腾一会,会感觉精力都花在了一起辅助工作上。

优化

针对这个问题,我们专门优化了 framework 的个数,将相似业务的 framework 进行了合并,最终 framework 的数量减少到了10个一下,组件之间的依赖图变成这样:

优化之后效果也很明显,我们对于各个framework的维护,变得简单多了。

两个彩蛋

另外在优化的过程,有两个值得一提的是:

  1. 之前“信息流“和”推送“还存在环状依赖的问题,就会导致当你想把 ”推送“的framework合并到业务中心的时候,竟然还得让业务中心去依赖信息流,这个当然是无法忍受的,解决办法也比较经典,就是让依赖下沉,把 Push 依赖的信息流的协议,放到了业务中心中。
  2. 这次的优化,其实是将”松散“变成了”耦合“,和我们平时常提到的观点刚好相反,但是确实是我们当前甚至今后一段时间内做所以我想说的是,”耦合“其实只是一个特征,虽然大部分情况是缺陷的特征,但是当耦合成为需求的时候,耦合就不是缺陷了。(有没有一点在哪里听过的感觉)

小结

我们谈”架构“的时候,说的最多的就是”取舍“,什么叫”取舍“,就是说你不能很简单的就判别出哪个是好的,哪个是不好的,总是觉得有点左右为难。而如何取舍?业务就是非常重要的一个标杆,只有结合业务,才能判断出哪个是最适合自己的。我们结合了业务,对自己的 framework 的数量进行了精简,当然也可能会根据业务的变化,在未来某天,需要将现有的framework拆分的更细。

第二个案例:饿了么移动APP的架构演进 —— 形成体系的认识

你做的项目,技术架构是怎么样的?

几乎所有人在被面试或者面试别人的的时候,都会(被)问到这个问题,很多人会回答,我们架构是MVC(MVVM),少数人还会使用MVP或者VIPER,我们姑且都称为MV(X),但是真的架构仅仅就是MV(X)吗?其实我觉得MV(X)虽然是架构中比较重要的部分,但是还是远远不能说架构 = MV(X)。

为什么呢?带着这个问题,我们来看第二个例子,在这个案例中,我们关注下面几点:

  1. 架构是如何随着业务的变化而变化的(这个也是对上面观点的一个证实)。
  2. 我们谈到架构就提的 MV(X),处于架构中的哪个部分。
  3. 通过”饿了么“的架构演变,体会一下每个阶段的侧重点是什么,对于架构有一个体系上的认识。

文章地址:

饿了么移动APP的架构演进
https://www.jianshu.com/p/2141fb0dc62c

”饿了么“的架构经历了4个阶段的演化:

  1. 第一阶段 MVC
  2. 第二阶段 Module Decoupled (组件化)
  3. 第三阶段 Hybrid
  4. 第四阶段 React-Native & Hot Patch

第一阶段 MVC

这个古老而经典的模式,不用多说。它是一个软件”从无到有“,”短平快“开发的首选。也是大部分规模比较小的 App 几乎大部分时间精力都会与之打交道的一个架构,以至于人们提架构比弹MVC。

当然这个架构随着业务的剧增,很快就会出现弊端,朝着Massive-View-Controller的方向奔去。

第二阶段 Module Decoupled

随着代码量不断增加,功能模块越来越多,不管是分工开发协作,还是已有模块的复用和维护,组件化都成了这个阶段的重点。组件化有个两个关键:

  1. 如何划分组件。
  2. 如何实现组件之间的通信。

对于第一个问题,”饿了么“采用的方案,基本是业界广为使用的分类方案,将组件分为共有组件和业务组件,

  • 公共组件提供了一些业务无关的基础服务:比如网络库、数据库、JSONModel等
  • 业务组件则对应具体的一块业务,比如登录业务组件,订单组件等

对于两种组件的管理:

  • 对于公共组件,使用CocoaPods进行版本管理(这点和我们目前不太一样,因为我们是SDK提供方,我们引用的第三方库,不确定我们的SDK使用方是否使用,是否更改源码,所以我们的方式,是将稳定版本的源码,混淆后打包进我们的代码)。
  • 对于业务组件,这个和我们大体类似,采取了业务模块注册机制的方式来达到解耦的目的,每个业务模块对外提供相应的业务接口,再启动时向一个中心注册自己的Scheme(我们是协议)。

而在具体某个业务组件内部,则可以根据不同开发人员,不同队伍的偏好,来实现不同的代码架构,比如MVC、MVVM、MVP等,也都不会影响整体系统架构。

这时的架构图,看上去长这样:

我们可以看到,MV(X) 已经不是关注的全部了,很多模块已经和 MV(X) 不怎么搭边了。

所以说,架构不等于 MV(X),其实 MV(X) 关注的只是”应用层“的部分

关于分层:
一般的,可以将App分为三层:应用层、service层、data access层。

  • 应用层 是直接和用户打交道的部分,我们常用到的 UIViewController,Android的 Activity,负责了数据的展示、流向、用户交互的处理。
  • service 层 是在应用层的下面,为应用层服务器的,对于应用层来说就像一个API调用延迟为0ms的Server API。一般会放在应用层的代码:网络接口调用、公共系统服务API(GPS定位、隐私权限访问)、一些 UTil 代码(所以我觉得比如一个 UIViewController 的一些私有方法和一些提工具性质的category,其实应该算serveice 层)。
  • data access 提供和对于数据的”增删改查“的接口层。

第三阶段 Hybrid

业务的变化又来啦,当用户规模达到比较大的数量,这次不仅仅是功能的增加,每两周一版已经满足不了产品、运营躁动的心了;同时,用纯 Native 代码编写的 App,如果上线后有错误,只能等下一次提交市场。在如今互联网竞争如此激烈的时代,一次线上错误有时也会带来很大的影响。所以这时候,很多纯粹展示性的模块会使用 H5 的方式来实现。

但是这种方式也有它的弊端:

  • 每次加载页面需要请求服务器,渲染时间比较长。
  • 调用本地硬件设备存在一定的不便。

对于这个问题,也有很多方案可以权衡,比如可以提前将网页打包好,以减少网络传输的时间,同时提供一系列的插件来访问本地的硬件设备。

“饿了么”这里的做法是,综合了 Native 和 H5 的优缺点,将页面做了一个划分,纯粹展示性的模块使用 H5;而更多的数据操作、动画渲染性的模块使用 Native。

架构图长成这样子:

业务再一次再架构的演化中扮演了重要的角色。

第四阶段 React-Native & Hot Patch

又要频繁迭代,又要用户体验,这时就考虑到了RN;另外,饿了么这个阶段用户已经过亿,线上一个小 bug 都可能影响几万人的使用,所以这个阶段,重点在于 RN 模块的引入,以及 Hot Patch 热修复功能的引入。

在 RN 的使用方面,依然有一个取舍,要回答下面的问题:

  • 哪些页面使用 RN,哪些页面不用 RN。
  • 是整个模块使用 RN,还是一个模块的部分页面使用 RN。
  • RN 和 Native 页面是2选1的关系,还是说是一个备份。
  • RN 和 Native 页面如何通信。

“饿了么”的做法是:对于20%最重要的页面,做了一个 RN 的镜像,也就是一个备份,然后通过服务器的配置,来切换Native 还是 RN,这样如果 Native 页面出现问题的时候,先通过开关将线上的页面切换成 RN,先保证线上正常使用,然后使用 Hot Patch 完成修补后,再切换回 Native App 原生页面。

这时的架构图:

不得不说,这种做法不一定适合别的团队,毕竟一个页面,要写 Native 、 RN 两套代码,并且要一直维护,花的代价都有点大,不是每个团队都有精力去这么搞的。其实这点,也正说明了,你需要根据自己业务,设计出一个最适合自己项目的架构。

小结

小结一下:

  1. 业务一直在影响架构的变迁。
  2. MV(X) 其实只是“应用层”的事,对于架构应该有个系统的认识。
  3. 架构的设计,并不是有现成的拿来用就 OK 的事,还有很多细节的部分需要做取舍,依赖业务需求和经验。

第三个案例:《猿题库 iOS 客户端架构设计》—— 好的架构具有哪些特质

第三个案例我们回归 MV(X),毕竟它确实是我们日常开发接触比较多的一部分。

对了这个案例,想关注的“点”是

  1. MVC 和 MVVM 的优缺点。
  2. 如何能够规避缺点,结合优点,改进架构,设计一个适合自己的MV(X)架构。
  3. 这个思想的底层原理是什么,在别的场景下的设计能够通用。

文章地址:

猿题库 iOS 客户端架构设计
http://gracelancy.com/blog/2016/01/06/ape-ios-arch-design/

MVC

优点:

  1. 易理解,对应现实生活中也是这样的。
  2. 易上手,iOS、Android 默认就是个 MVC 的环境。

缺点:

  1. 当指责不是那么明确,不知道该放哪时,代码就会被放在"Controller"里面吧,Controller越来越难维护。

其实对于上面这个缺点,唐巧也在一篇文章中写道,这个问题其实也不能说是 MVC 的缺点,是我们没有拆分好代码。可以看看唐巧的《被误解的 MVC 和被神化的 MVVM》,提出了一些如何解决 Controler 臃肿的解决办法,然后也表达了对于 MVVM 的质疑,具体的做法可以去读这篇文章。这也正说明了大家对于架构的理解和态度真的是有区别的。

MVVM

具体关于MVVM的概念可以参考 Objc 的《MVVM 介绍》,这里就不具体说 MVVM 的概念了。

不了解MVVM的同学,知道这几点就行:

  1. MVVM将ViewController视作View。
  2. 关于 View Model,只需要知道两件事:持有model;View可以完全通过一个View Model决定自己如何展示。
  3. View 和 View Model,View Model 和 Model之间通过数据绑定,使得 Model改变的时候,能同步更新 View Model,进而更新 View。

优点:

  1. 减轻了 Controller 的负担,拆分了代码
  2. View Model有比较好的测试性。
  3. 结合 RAC, 可以将数据和 View 通信的代码精简到很少。

缺点:

  1. 上手成本高。
  2. 由于使用数据绑定,界面的 bug 变的不易调试。
  3. ViewModel 接管了 ViewController 的大部分职责,慢慢也可能变的臃肿。

综合两者

来看下 Lancy 的设计,是如何将两者综合,规避缺点,保留优点的,先上图:

对于上图的说明:

  • 一个View Controller 持有一个 Data Controller。
  • Data Controller,是数据管理模块,负责数据的生命周期:获取、保存、更新。
  • 一个 View Controller 里面有多个 View,每个 View 对应一个 View Model,这里的 View Model 概念和 MVVM 里的类似,唯一不同的是这里的 View Model 和 Model ,没有绑定机制。
  • View 的展示样式,完全决定于 View Model。

结合产品 UI,再按照数据流的方式阐述,以下面的 CollectionView 为例。

  1. View Controller 持有 一个 Data Controller,初始化之后,调用 Data Controller 获取用户打开的课程。(1)
  2. Data Controller 通过 API 获取数据,封装成 Model 并返回 (2,3)
  3. View Controller 将2中返回的数据,生成 View Model,调用 View 的 bindDataWithViewModel 方法装配给对应的 View。(4)
  4. View Controller 会调用 View 的渲染方法,View 通过 View model 直接进行渲染。(5)
  5. 如果有用户事件,通过代理的方式,传递给 View Controller,让View Controller 来决定下一步的处理。(6)

这么方式的优缺点:

优点:

  1. 指责分明,确定给 Controller 肩负。
  2. 耦合度低,测试性高。指责分明带来的效果就是耦合度低,同一个功能,可以分别由不同的开发人员分别进行开发界面和逻辑,只需要确立好接口即可。
  3. 学习成本低,不用事件绑定,不需要学习 RAC。
  4. 易于调试 Bug,不使用绑定带来的好处。

缺点:

  1. 当页面的交互逻辑非常多时,需要频繁的在 DC-VC-VM 里来回传递信息,造成了大量胶水代码。
  2. 没有绑定,带来额外的代码(绑定真的是双刃剑)

小结

针对这个案例,我觉得最应该我们思考的就是,作者 Lancy,在设计架构的时候的思路是怎么样的?为什么要那么设计?是怎么取舍的?总结一下:

  1. 因为想让团队能够快速上手,以及bug可以快速调试,所以没有使用绑定机制。(从学习成本、开发成本、以调试角度)
  2. 依然保证了指责的明确划分。(好的架构一定要明确划分职责,甚至均衡的划分)
  3. 方便测试依然是重要的一个设计标准。(好的架构要易于测试)
  4. 还有相当重要的一个标准——解耦(好的架构要易于维护,解耦意味着比较易于维护)
  5. 上面提到了缺点之一是“当页面交互逻辑非常多时,会不太合适”,这也说明了,作者采用了这个架构,其实是基于页面交互不是很多的情况(用户交互确实带来Model的改动不是很多,当前界面并不能修改用户所开的课程)。所以业务依然是影响架构设计的总要因素。
  6. 还有一点不知道大家有没有在意,上面提到了“数据流”,对着这个架构我们能清晰的说出“数据流”,这个我认为也是一个好的架构应该具有的特性。数据流如果很模糊,有很多分支,那我们的维护成本将大大增加,一个清晰的数据流,意味着你无论在这个流的那个节点继续执行下需,都能得到正确的结果。

基于对这个案例的分析,最应该思考的是,设计一个架构的思路,换言之,你要心里明白,怎样才是一个好的架构。

总结

总结一下,今天说的这三个案例,其实就是为了说明一下几点:

  1. 懂业务对于架构的重要性。
  2. 架构 != MV(X),站在更加宏观的角度看问题,对于打开思路更有帮助。
  3. 当我们设计架构的时候,怎样才是一个好的架构。

其实“架构”真的是个很大的话题,很多知识都可以拿出来单独学习和分享。

  • 设计原则和设计模式(设计的基本功)
  • 数据结构和算法(设计的基本功)
  • MV(X)/Viper
  • 组件化 (光这个就特别多可以讲的)
  • 网络层的架构设计(比如离散的还是集约的)
  • 持久化层的设计
  • Hybrid 的设计
  • RN、Hot Patch
  • 无埋点
  • pod 私有库维护 SDK
  • 面向过程/面向对象
  • AOP
  • 。。。。。

希望以后能陆续的为大家分享,擅长哪个方向,或者对哪个方向感兴趣的小伙伴也可以给大家分享一下,让大家的设计能力一点点提高上来。

参考

MVVM 介绍
https://objccn.io/issue-13-1/
被误解的 MVC 和被神化的 MVVM
http://blog.devtang.com/2015/11/02/mvc-and-mvvm/
iOS应用架构谈系列
https://casatwy.com/iosying-yong-jia-gou-tan-kai-pian.html
猿题库 iOS 客户端架构设计
http://gracelancy.com/blog/2016/01/06/ape-ios-arch-design/
饿了么移动APP的架构演进
https://www.jianshu.com/p/2141fb0dc62c
iOS应用层架构之CDD
http://mrpeak.cn/blog/cdd/
iOS应用架构现状分析
http://mrpeak.cn/blog/ios-arch/
iOS 架构模式–解密 MVC,MVP,MVVM以及VIPER架构
http://www.cocoachina.com/ios/20160108/14916.html

posted @ 2019-04-09 16:42  张驰小方块  阅读(2174)  评论(0编辑  收藏  举报