我们常见的分层架构,有三层架构:视图层、业务层、数据层。也有四层架构:视图层、业务层、网络层、本地数据层。
这里说三层、四层,跟TCP/IP所谓的五层或者七层不是同一种概念。再具体说就是:你的架构在逻辑上设计的是几层那就是几层,具体每一层的名称和作用,没有特定的规范, 这主要是针对模块分类而言的。

也有说MVC架构,MVVM架构的,这种层次划分,主要是针对数据流动的方向而言的。
在实际情况中,针对数据流动方向做的设计和针对模块分类做的设计是会放在一起的,也就是说,一个MVC架构可以是四层:展现层、业务层、网络层、本地数据层。
那么,为什么我要说这个?
大概在五六年前,业界很流行三层架构这个术语。然后各种文档资料漫天的三层架构,并且喜欢把它与MVC放在一起说,MVC三层架构/三层架构MVC,以至于很多人就会认为三层架构就是MVC,MVC就是三层架构。其实不是的。三层架构里面其实没有Controller的概念,而且三层架构描述的侧重点是模块之间的逻辑关系。MVC有Controller的概念,它描述的侧重点在于数据流动方向。

好,为什么流行起来的是三层架构,而不是四层架构或五层架构?

因为所有的模块角色只会有三种:数据管理者、数据加工者、数据展示者,意思也就是,笼统说来,软件只会有三层,每一层扮演一个角色。其他的第四层第五层,一般都是这三层里面的其中之一分出来的,最后都能归纳进这三层的某一层中去,所以用三层架构来描述就比较普遍。

1.视图层设计方案
2.网络层设计方案
3.本地持久化方案
4.动态部署方案
上面这四大点,稍微细说一下就是:

  • 页面如何组织,才能尽可能降低业务方代码的耦合度?尽可能降低业务方开发界面的复杂度,提高他们的效率?
  • 如何让业务开发工程师方便安全地调用网络API?然后尽可能保证用户在各种网络环境下都能有良好的体验?
  • 当数据有在本地存取的需求的时候,如何能够保证数据在本地的合理安排?如何尽可能地减小性能消耗?
  • iOS应用有审核周期,如何能够通过不发版本的方式展示新的内容给用户?如何修复紧急bug?

一个好的架构

  • 遵循代码规范代码,分类明确(没有难以区分模块的文件夹或模块)
  • 注释明了, 逻辑清晰, 不用文档,或很少文档,就能让业务方上手
  • 思路和方法要统一,尽量不要多元
  • 没有横向依赖,尽可能少的跨层访问
  • 对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件
  • 易测试,易拓展
  • 保持一定量的超前性
  • 接口少,接口参数少
  • 低内存,高性能

遵循代码规范代码,分类明确(没有难以区分模块的文件夹或模块)

代码整齐是每一个工程师的基本素质,先不说你搞定这个问题的方案有多好,解决速度有多快,如果代码不整齐,一切都白搭。因为你的代码是要给别人看的,你自己也要看。如果哪一天架构有修改,正好改到这个地方,你很容易自己都看不懂。另外,破窗理论提醒我们,如果代码不整齐分类不明确,整个架构会随着一次一次的拓展而越来越混乱。
分类明确的字面意思大家一定都了解,但还有一个另外的意思,那就是:不要让一个类或者一个模块做两种不同的事情。如果有类或某模块做了两种不同的事情,一方面不适合未来拓展,另一方面也会造成分类困难。
不要搞Common,Core这些东西。每家公司的架构代码库里面,最恶心的一定是这两个名字命名的文件夹,我这么说一定不会错。不要开Common,Core这样的文件夹,开了之后后来者一定会把这个地方搞得一团糟,最终变成Common也不Common,Core也不Core。要记住,架构是不断成长的,是会不断变化的。不是每次成长每次变化,都是由你去实现的。如果真有什么东西特别小,那就索性为了他单独开辟一个模块就好了,小就小点,关键是要有序。

不用文档,或很少文档,就能让业务方上手

谁特么会去看文档啊,业务方他们已经被产品经理逼得很忙了。所以你要尽可能让你的API名字可读性强,对于iOS来说,objc这门语言的特性把这个做到了极致,函数名长就长一点,不要紧。

1
2
3
4
5
6
7
8
9
10
好的函数名:
- (NSDictionary *)exifDataOfImage:(UIImage *)image atIndexPath:(NSIndexPath *)indexPath;
坏的函数名:
- (id)exifData:(UIImage *)image position:(id)indexPath callback:(id<ErrorDelegate>)delegate;
为什么坏?
1. 不要直接返回id或者传入id,实在不行,用id<protocol>也比id好。如果连这个都做不到,你要好好考虑你的架构是不是有问题。
2. 要告知业务方要传的东西是什么,比如要传Image,那就写上ofImage。如果要传位置,那就要写上IndexPath,而不是用position这么笼统的东西
3. 没有任何理由要把delegate作为参数传进去,一定不会有任何情况不得不这么做的。而且delegate这个参数根本不是这个函数要解决的问题的充要条件,如果你发现你不得不这么做,那一定是架构有问题!

思路和方法要统一,尽量不要多元

解决一个问题会有很多种方案,但是一旦确定了一种方案,就不要在另一个地方采用别的方案了。也就是做架构的时候,你得时刻记住当初你决定要处理这样类型的问题的方案是什么,以及你的初衷是什么,不要摇摆不定。
另外,你当初设立这个模块一定是有想法有原因的,要记录下你的解决思路,不要到时候换个地方你又灵光一现啥的,引入了其他方案,从而导致异构。
要是一个框架里面解决同一种类似的问题有各种五花八门的方法或者类,我觉得做这个架构的架构师一定是自己都没想清楚就开始搞了。

没有横向依赖,万不得已不出现跨层访问

没有横向依赖是很重要的,这决定了你将来要对这个架构做修补所需要的成本有多大。要做到没有横向依赖,这是很考验架构师的模块分类能力和是否熟悉业务的。
跨层访问是指数据流向了跟自己没有对接关系的模块。有的时候跨层访问是不可避免的,比如网络底层里面信号从2G变成了3G变成了4G,这是有可能需要跨层通知到View的。但这种情况不多,一旦出现就要想尽一切办法在本层搞定或者交给上层或者下层搞定,尽量不要出现跨层的情况。跨层访问同样也会增加耦合度,当某一层需要整体替换的时候,牵涉面就会很大。

对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件

把这点做好,很依赖于架构师的经验。架构师必须要有能力区分哪些情况需要限制灵活性,哪些情况需要创造灵活性。比如对于Core Data技术栈来说,ManagedObject理论上是可以出现在任何地方的,那就意味着任何地方都可以修改ManagedObject,这就导致ManagedObjectContext在同步修改的时候把各种不同来源的修改同步进去。这时候就需要限制灵活性,只对外公开一个修改接口,不暴露任何ManagedObject在外面。
如果是设计一个ABTest相关的API的时候,我们又希望增加它的灵活性。使得业务方不光可以通过Target-Action的模式实现ABtest,也要可以通过Block的方式实现ABTest,要尽可能满足灵活性,减少业务方的使用成本。

易测试易拓展

老生常谈,要实现易测试易拓展,那就要提高模块化程度,尽可能减少依赖关系,便于mock。另外,如果是高度模块化的架构,拓展起来将会是一件非常容易的事情。

保持一定量的超前性

这一点能看出架构师是否关注行业动态,是否能准确把握技术走向。保持适度的技术上的超前性,能够使得你的架构更新变得相对轻松。
另外,这里的超前性也不光是技术上的,还有产品上的。谁说架构师就不需要跟产品经理打交道了,没事多跟产品经理聊聊天,听听他对产品未来走向的畅想,你就可以在合理的地方为他的畅想留一条路子。同时,在创业公司的环境下,很多产品需求其实只是为了赶产品进度而产生的妥协方案,最后还是会转到正轨的。这时候业务方可以不实现转到正规的方案,但是架构这边,是一定要为这种可预知的改变做准备的。

接口少,接口参数少

越少的接口越少的参数,就能越降低业务方的使用成本。当然,充要条件还是要满足的,如何在满足充要条件的情况下尽可能地减少接口和参数数量,这就能看出架构师的功力有多深厚了。

高性能

为什么高性能排在最后一位?
高性能非常重要,但是在客户端架构中,它不是第一考虑因素。原因有下:

  • 客户端业务变化非常之快,做架构时首要考虑因素应当是便于业务方快速满足产品需求,因此需要尽可能提供简单易用效果好的接口给业务方,而不是提供高性能的接口给业务方。
  • 苹果平台的性能非常之棒,正常情况下很少会出现由于性能不够导致的用户体验问题。
  • 苹果平台的优化手段相对有限,甚至于有些时候即便动用了无所不用其极的手段乃至不择手段牺牲了稳定性,性能提高很有可能也只不过是100ms到90ms的差距。10%的性能提升对于服务端来说很不错了,因为服务端动不动就是几十万上百万的访问量,几十万上百万个10ms是很可观的。但是对于客户端的用户来说,他无法感知这10ms的差别,如果从10s优化成9s用户还是有一定感知的,但是100ms变90ms,我觉得吧,还是别折腾了。

但是!不重要不代表用不着去做,关于性能优化的东西,我会对应放到各系列文章里面去。比如网络层优化,那就会在网络层方案的那篇文章里面去写,对应每层架构都有每层架构的不同优化方案,我都会在各自文章里面一一细说。

一、视图层设计方案

一般来说,一个不够好的View层架构,主要原因有以下五种:
1.代码混乱不规范
2.过多继承导致的复杂依赖关系
3.模块化程度不够高,组件粒度不够细
4.横向依赖
5.架构设计失去传承

View层的代码结构规范

制定代码规范严格来讲不属于View层架构的事情,但它对View层架构未来的影响会比较大,也是属于架构师在设计View层架构时需要考虑的事情。制定View层规范的重要性在于:
1.提高业务方View层的可读性可维护性
2.防止业务代码对架构产生腐蚀
3.确保传承
4.保持架构发展的方向不轻易被不合理的意见所左右

最后,什么是优秀的架构,这的确是一个非常艰难的话题。万能药是不存在的。我不认为有某种架构能够解决所有问题,我觉得应该根据从客户那里收集的需求和用例来考虑架构的事情。因此,根据需求,架构很可能会与我所使用的完全不同。现在,我默认会使用 MVVM 架构,而今后,ReSwift 或者其他架构可能会更加合适。编程的好处就在于我们每天都需要学习,两年后我的观点可能与现在大相径庭。如果不是这样的话,那么我就会担心是不是我学得东西还不够多。