架构没有好坏之分,合适的架构就是好的架构。在选择一个合适的架构方式前,要清楚需要做的事情、解决什么问题、业务方面需要得到什么,脱离业务谈架构就是纯粹的耍流氓。
架构原则:易读性、易维护性、易扩展性。
本章会先介绍架构模式,在此基础上讨论组件化;然后讨论下混合开发;最后梳理下设计模式。
架构模式
架构模式的出现时为了管理复杂的应用程序,这样可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。我们经常说的MVC架构、MVVM架构属于此类。
MVC
传统移动APP开发的MVC架构:
1、短平快,实用于快速上线抢占市场
2、这种架构以层次结构简单清晰,代码容易开发而被大多数人所接受。
在MVC的体系架构中,Controller层负责整个APP中主要逻辑功能的实现;Model层则负责数据结构的描述以及数据持久化的功能;而View层作为展现层负责渲染整个APP的UI。分工清晰,简洁明了;并且这种系统架构在语言框架层就得到了Apple的支持,所以非常适用于APP的startup开发。然后,这种架构在开发的后期会由于其超高耦和性,从而造就庞大Controller层,而这也是一直被人所诟病。最终的MVC都从Model-View-Controller走向了Massive-View-Controller 的终点。
当业务发展到一定层次,单一主营业务或单一App发展为百花齐放百家争鸣的多元业务格局,MVC的弊端就开始无限放大,臃肿的Controller层会呈现出几千行代码,对于喜欢一个函数只做一件事的我而言,是完全无法接受的,也许有的人会选择采用类别切分成多个Controller来假性“解决”。这时候,降低耦合,复用已有模块成了架构的第一要务。
无论哪种架构,都能以MVC为基准,不断调整重构、不断划分职责、不断细化得来的。所以,能够掌握如何划分职责,将视图、逻辑、数据三者连接起来,易用并方便维护,那么就可以了,无所谓什么模式。
MVP
作为MVC的进阶版, 提出区分业务逻辑和业务展示, 将所有的业务逻辑转移到P层, V层接受P层的数据更新通知进行页面展示. 优点在于良好的分层带来了友好的单元测试, 缺点在于分层会让代码逻辑优点绕, 同时也带来了大量的代码工作, 对程序员不够友好.
MVVM
数据绑定做数据更新, 减少了大量的代码工作, 同时优化了代码逻辑, 只是学习成本有点高, 对新手不够友好.
依照需求搭建你的iOS架构
对于绝大多数iOS开发者而言,日常的工作就是重复“网络请求-》数据存储-》页面展示和交互”。再多一点就是埋点收集用户数据,提供给产品或运营;自动化打包或自动化测试...
如何进行架构设计?
- 搞清楚要解决那些问题,找到问题的充要条件
- 问题分类,分模块
- 推演未来可能的走向,以备未来之需
- 解决依赖关系中最基础的问题,实现基础模块,然后再用基础模块堆叠出整个架构
- 打点,跑单元测试,跑性能测试,对应优化
架构分层
常见的分层架构,有三层架构的:展现层、业务层、数据层。也有四层架构的:展现层、业务层、网络层、本地数据层。
举个例子:你要设计一个即时通讯的服务端架构,怎么分层?
记住,不要一上来就把三层架构的规范套上去,这样做是做不出好架构的。你要先确定都需要解决哪些问题。这里只是举例子,我随意列出一点意思意思就好了:
要解决用户登录、退出的问题
解决不同用户间数据交流的问题
解决用户数据存储的问题
如果是多台服务器的集群,就要解决用户连接的寻址问题
解决第一个问题需要一个链接管理模块,链接管理模块一般是通过链接池来实现。 解决第二个问题需要有一个数据交换模块,从A接收来的数据要给到B,这个事情由这个模块来做。 解决第三个问题需要有个数据库,如果是服务于大量用户,那么就需要一个缓冲区,只有当需要存储的数据达到一定量时才执行写操作。 解决第四个问题可以有几种解决方案,一个是集群中有那么几台服务器作为寻路服务器,所有寻路的服务交给那几台去做,那么你需要开发一个寻路服务的Daemon。或者用广播方式寻路,但如果寻路频次非常高,会造成集群内部网络负载特别大。这是你要权衡的地方,目前流行的思路是去中心化,那么要解决网络负载的问题,你就可以考虑配置一个缓存。
于是我们有了这些模块:
链接管理、数据交换、数据库及其配套模块、寻路模块
做到这里还远远没有结束,你要继续针对这四个模块继续往下细分,直到足够小为止。但是这里只是举例子,所以就不往下深究了。
业务展示
业务层和展示层主要是要注意业务耦合度和基础模块的封装。在此基础上采用组件化。
网络请求
网络层在一个App中也是一个不可缺少的部分,工程师们在网络层能够发挥的空间也比较大。另外,苹果对网络请求部分已经做了很好的封装,业界的AFNetworking也被广泛使用。加入说需要你自己搭建一个HTTP框架,你需要考虑哪些模块呢?
1、网络层的安全机制;
思路:设计签名,即在请求中携带同服务器商量好的密钥hash出来的字符串。同时采用安全的HTTPS机制;
2、网络层跟业务对接API的设计
思路:以什么方式传送数据(delegate、notification、block、KVO、target-Action)、传送什么样的数据给业务层(一般APP从服务器得到的数据是Json格式,很多APP直接转化成对象)
3、网络层的优化方案
思路:连接复用、缓存
数据存储
持久化方案不管是服务端还是客户端,都是一个非常值得讨论的话题。尤其是在服务端,持久化方案的优劣往往都会在一定程度上影响到产品的性能。然而在客户端,只有为数不多的业务需求会涉及持久化方案,而且在大多数情况下,持久化方案对性能的要求并不是特别苛刻。所以我在移动端这边做持久化方案设计的时候,考虑更多的是方案的可维护和可拓展,然后在此基础上才是性能调优。
- 根据需求决定持久化方案
- 持久层与业务层之间的隔离
- 持久层与业务层的交互方式
- 数据迁移方案
- 数据同步方案
思路:可维护性、可扩展性、性能消耗
在有需要持久化需求的时候,我们有非常多的方案可供选择:NSUserDefault、KeyChain、File,以及基于数据库的无数子方案。因此,当有需要持久化的需求的时候,我们首先考虑的是应该采用什么手段去进行持久化。
NSUserDefault
一般来说,小规模数据,弱业务相关数据,都可以放到NSUserDefault里面,内容比较多的数据,强业务相关的数据就不太适合NSUserDefault了。我就见到过有些业务线会把大部分业务数据都塞到NSUserDefault里面去,当时看代码的时候我特么就直接跪了。。。问起来为什么这么做?结果说因为写起来方便~你妹。。。keychain
Keychain是苹果提供的带有可逆加密的存储机制,普遍用在各种存密码的需求上。另外,由于App卸载只要系统不重装,Keychain中的数据依旧能够得到保留,以及可被iCloud同步的特性,大家都会在这里存储用户唯一标识串。所以有需要加密、需要存iCloud的敏感小数据,一般都会放在Keychain。
- 文件存储
文件存储包括了Plist、archive、Stream等方式,一般结构化的数据或者需要方便查询的数据,都会以Plist的方式去持久化。Archive方式适合存储平时不太经常使用但很大量的数据,或者读取之后希望直接对象化的数据,因为Archive会将对象及其对象关系序列化,以至于读取数据的时候需要Decode很花时间,Decode的过程可以是解压,也可以是对象化,这个可以根据具体中的实现来决定。Stream就是一般的文件存储了,一般用来存存图片啊啥的,适用于比较经常使用,然而数据量又不算非常大的那种。
- 数据库存储
数据库存储的话,花样就比较多了。苹果自带了一个Core Data,当然业界也有无数替代方案可选,不过真正用在iOS领域的除了Core Data外,就是FMDB比较多了。数据库方案主要是为了便于增删改查,当数据有状态和类别的时候最好还是采用数据库方案比较好,而且尤其是当这些状态和类别都是强业务相关的时候,就更加要采用数据库方案了。因为你不可能通过文件系统遍历文件去甄别你需要获取的属于某个状态或类别的数据,这么做成本就太大了。当然,特别大量的数据也不适合直接存储数据库,比如图片或者文章这样的数据,一般来说,都是数据库存一个文件名,然后这个文件名指向的是某个图片或者文章的文件。如果真的要做全文索引这种需求,建议最好还是挂个API丢到服务端去做。
功能组件化
功能组件化:将拥有独立功能的代码从系统中进行抽象并剥离,再以“插件”的形式插回原有系统中。剥离出功能组件,降低系统中模块与模块之间的耦和性,同时提高APP之间代码的复用性。比如,广告作为很多公司盈利的主要来源,抽取出一个广告插件,使用的地方灵活配置绝对是所有人的做法。
当然,组件化一般情况下都会分为公有组件和业务组件。
- 公有组件
指的是封装得比较好的一些SDK,包括一些第三方组件和自己内部使用的组件,比如人人称道的AFNetworking、SDwebImage,都是这类组件的代表。
- 业务组件
则定义为包含了一系列业务功能的整体,例如登录业务组件,注册业务组件,账户业务组件即为此类组件的典型代表。对于业务组件,前期一般多会在使用的页面生成class、传递参数、跳转页面。一种有效改良的方法是“采取了业务模块注册机制”来解除耦合,每个业务模块对外提供相应的业务接口和短链接,短链接专门用一个类来映射对应的处理方法,灵活方便。而在业务组件,即业务模块的内部,则可以根据不同开发人员的偏好,来实现不同的代码架构。如上面讨论的MVVM, MVP等,都可以在模块内部进行而不影响整体系统架构。
- 组件化示例
项目的初期,不管用那种架构,我们经常会采用模块之间互相调用,甚至模块划分不清晰,如图:
造成严重耦合,为了解决上面的问题,可以考虑加一个中间层来协调模块间的调用,所有的模块间的调用都会经过中间层中转。如下:
在组件进化过程中,很多大厂都提出了技术方案
- 蘑菇街MGJRouter
- casatwy组件化
- 滴滴组件化架构
- 淘宝组件化架构
Hybrid
移动APP的开发有两种不同的路线,Native APP和Web APP。这两种路线的区别类似于PC时代开发应用程序时的C/S架构和 B/S架构。
Native APP
即所有的程序都由本地组件渲染完成。这类APP优点是显而易见的,渲染速度快、用户体验好;缺点同时也十分突出:出现了错误一定要等待下一次用户进行APP更新才能够修复。Web APP
优点恰好就是Native APP的缺点所在,其页面全部采用H5撰写并存放在服务器端。每次进行页面渲染时都从服务器请求最新的页面。一旦页面有错误服务器端进行更新便能立刻解决。不过其弊端也容易窥见:每次页面都需要请求服务器,造成渲染时等待时间过长,从而导致的用户体验不够完美,并且性能上较Native APP慢了1-2个数量级;与此同时还会导致更多的用户流量消耗。另一个缺点则在于,Web APP在移动端上调用本地的硬件设备存在一定的不便。不过这些弊端也都有相应的解决方案,如PhoneGap将网页提前打包在本地以减少网络的请求时间;同时也提供一系列的插件来访问本地的硬件设备。然而,尽管如此,其渲染速度上还是会稍微存在一定的差距。Hybrid APP
则是综合了二者优缺点的解决方案。纯粹展示性的模块会更适合使用Web页面来达到渲染的目的;而更多的数据操作性、动画渲染性的模块则更适合采用Native的方式。
在Hot Patch被苹果爸爸强制截断,Hybrid会被推向一个新高度。是的,完全无法想象,出现bug,特别是重大问题后,你只能束手无策的提交新版本,耐心等待审核人员通过,告知用户升级新版本。而典型的Hybrid,得到行业认可的是Facebook开源的React-Native,当然,目前国产大公司也有很多建树,某宝的weex,我司已经开始大量投入使用(略微担心苹果亲爸整出什么幺蛾子)。
也有很多H5直接和Native交互,以JSBridge的方式连接的方案进行动态部署,有名的CTJSBridge。
设计模式
设计模式可以通俗的理解为实现/解决某些问题,而形成的解决方案规范。增加代码的可重用性,让代码能更容易理解和可靠。我们通常说所的代理模式、迭代器模式、策略模式就属于这一类。对各种设计模式的了解可以帮助我们更快的解决编程过程中遇到的问题。
设计模式:设计模式主要分三个类型:创建型、结构型和行为型。传送门-23种设计模式详解
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。
其中iOS主要重点用到的是以下几种:
- 单例模式
当某个对象在整个程序中我们只需要一个,并且我们需要在不同的地方调用这个对象,获取其中的属性资源。这种时候我们就需要用到单例模式这种设计模式。
示例:
UIApplication类提供了 +sharedAPplication方法创建和获取UIApplication单例
NSBundle类提供了 +mainBunle方法获取NSBundle单例
NSFileManager类提供了 +defaultManager方法创建和获得NSFileManager单例。(PS:有些时候我们得放弃使用单例模式,使用-init方法去实现一个新的实例,比如使用委托时)
NSNotificationCenter提供了 +defaultCenter方法创建和获取NSNotificationCenter单例(PS:该类还遵循了另一个重要的设计模式:观察者模式)
NSUserDefaults类提供了 +defaultUserDefaults方法去创建和获取NSUserDefaults单例
** 一般我们习惯在定义方法share中使用dispatch_once函数(这个函数的作用就是保证block)来保证单例模式在整个程序中只被创建一次。但是之前看到有个同学是覆写+(instancetype)allocWithZone:(struct _NSZone *)zone方法中添加代码,保证只被创建一次。原因是别人可能并不知道你是单例,在生成的时候用[[class alloc]init]的形式,allocWithZone能保证不管哪种形式都能确保是单例。
观察者模式
一个对象状态改变,通知正在对他进行观察的对象,这些对象根据各自要求做出相应的改变。操作对象向被观察者对象投送消息,使得被观察者的状态得以改变,在此之前已经有观察者向被观察对象注册,订阅它的广播,现在被观察对象将自己状态发生改变的消息广播出来,观察者接收到消息各自做出应变。
示例:
KVO(Key-Value-Observing)机制:
通知(notification)机制,NSNotificationCenter代理模式
就像是java中的接口,类可以实现或不实现协议(接口)中的方法。
示例:
UITableViewDelegate UITableViewDataSource策略模式
简单工厂
“简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。”——百度百科
简单工厂详解及示例
- 工厂模式
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。---百度百科
定义创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到子类。
工厂方式应该算是我们经常都会使用到的一种设计模式了,例如OC中的NSNumber,NSNumber可以提供int、BOOL、Float等相关类型的工厂方法来生产基于特定接口的不同类型对象。工厂模式的直观意义可以理解为:通过固定工厂,生产不同形式的单一物品。例如我有一个轮胎工厂,我可以在工厂中生产适用于汽车、卡车、摩托车等适应的轮胎。
工厂模式详解及示例
- 抽象工厂模式
抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。---[百度百科]
- 适配器模式
在计算机编程中,适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。---百度百科
简而言之,就是引入一系列的操作取代之前对应的操作,同时又不影响以前的功能。
适配器模式模式详解及示例
一篇关于“几种设计模式对比”比较好的讲解:传送门