以前安居客iOS app的第二版架构大部份内容是我作的,期间有总结了一些经验。在将近一年以后,前同事zzz在微信朋友圈上发了一个问题:假如问你一个iOS or Android app的架构,你会从哪些方面来讲呢?
数据库
当时看到这个问题正好在乘公车回家的路上,闲来无聊就答了一把。在zzz在微信朋友圈上追问了几个问题以后,我以为有必要开个博客专门来说讲一些我的看法。编程
其实对于iOS客户端应用的架构来讲,复杂度不亚于服务端,但侧重点和入手点却跟服务端不太同样。好比客户端应用就不须要考虑相似C10K的问题,正常的app就根本不须要考虑。缓存
这系列文章我会主要专一在iOS应用架构方面,不少方案也是基于iOS技术栈的特色而创建的。由于我我的不是很喜欢写Java,因此Android 这边的我就不太了解了。若是你是Android开发者,你能够侧重看我提出的一些架构思想,毕竟无论作什么,思路是相通的,实现手段不一样罢了。安全
其实市面上大部分应用不外乎就是颠过来倒过去地作如下这些事情:性能优化
--------------- --------------- --------------- --------------- | | | | | | | | | 调用网络API | --> | 展示列表 | --> | 选择列表 | --> | 展示单页 | | | | | | | | | --------------- --------------- --------------- --------------- ^ | | | | | ------------------------------------------
简单来讲就是调API,展现页面,而后跳转到别的地方再调API,再展现页面。服务器
非也,非也。 ---- 包不一样 《天龙八部》微信
App确实就是主要作这些事情,可是支撑这些事情的基础,就是作架构要考虑的事情。网络
上面这四大点,稍微细说一下就是:架构
上面几点是针对App说的,下面还有一些是针对团队说的:app
一时半会儿我仍是只能想到上面这三点,事实上应该还会有不少,想不起来了。
因此当咱们讨论客户端应用架构的时候,咱们讨论的差很少就是这些问题。
这系列文章主要是回答如下这些问题:
上面细分出来的四个问题,我会分别在四篇文章里面写。那么这篇文章就是来说一些通识啥的,也是开个坑给你们讨论通识问题的。
全部事情最难的时候都是开始作的时候,当你开始着手设计并实现某一层的架构乃至整个app的架构的时候,颇有可能会出现暂时的无从下手的状况。如下 方法论是我这些年总结出来的经验,每一个架构师也必定都有一套本身的方法论,但同样的是,无论你采用什么方法,全局观、高度的代码审美能力、灵活使用各类设 计模式必定都是贯穿其中的。欢迎各位在评论区讨论。
第一步:搞清楚要解决哪些问题,并找到解决这些问题的充要条件
你必须得清楚你要作什么,业务方但愿要什么。而不是为了架构而架构,也不是为了体验新技术而改架构方案。之前是MVC,最近流行MVVM,若是过去的MVC是个好架构,没什么特别大的缺陷,就不要推倒而后搞成MVVM。
关于充要条件我也要说明一下,有的时候系统提供的函数是须要额外参数的,好比read函数。还有翻页的时候,当前页码也是充要条件。但对于业务方来讲,这些充要条件还可以再缩减。
好比read,须要给出file descriptor,须要给出buf,须要给出size。可是对于业务方来讲,充要条件就只要file descriptor就够了。再好比翻页,其实业务方并不须要记录当前页号,你给他暴露一个loadNextPage
这样的方法就够了。
搞清楚对于业务方而言的真正充要条件很重要!这决定了你的架构是否足够易用。另外,传的参数越少,耦合度相对而言就越小,你替换模块或者升级模块所花的的代价就越小。
第二步:问题分类,分模块
这个不用多说了吧。
第三步:搞清楚各问题之间的依赖关系,创建好模块交流规范并设计模块
关键在于创建一套统一的交流规范。这一步很可以体现架构师在软件方面的价值观,虽然存在必定程度上的好坏优劣(好比胖Model和瘦Model), 但既然都是架构师了,基本上是不会设计出明显很烂的方案的,除非这架构师还未入流。因此这里是架构师价值观输出的一个窗口,从这一点咱们是可以看出架构师 的素质的。
另外要注意的是,必定是创建一套统一的交流规范,不是两套,不是多套。你要坚持你的价值观,不要摇摆不定。要是搞出各类五花八门的规范出来,一方面有不切实际的炫技嫌疑,另外一方面也会带来后续维护的灾难。
第四步:推演预测一下将来可能的走向,必要时添加新的模块,记录更多的基础数据以备将来之需
不少称职的架构师都会在这时候考虑架构将来的走向,以及考虑作完这一轮架构以后,接下来要作的事情。一个好的架构虽然是功在当代利在千秋的工程,但绝对不是一个一劳永逸的工程。软件是有生命的,你作出来的架构决定了这个软件它这一辈子是坎坷仍是幸福。
第五步:先解决依赖关系中最基础的问题,实现基础模块,而后再用基础模块堆叠出整个架构
这一步也是验证你以前的设计是否合理的一步,随着这一步的推动,你颇有可能会遇到须要对架构进行调整的状况。这个阶段必定要吹毛求疵高度负责地去开发,不要得过且过,发现架构有问题就及时调整。不然之后调整的成本就很是之大了。
第六步:打点,跑单元测试,跑性能测试,根据数据去优化对应的地方
你得用这些数据去向你的boss邀功,你也得用这些数据去不断调整你的架构。
总而言之就是要遵循这些原则:自顶向下设计
(1,2,3,4步),自底向上实现
(5),先测量,后优化
(6)。
作不到这点,你就是码农
作不到这点,你就是运维
作不到这点,你比较适合去竞争对手那儿当工程师
作不到这点,公关行业比较适合你
作不到这点,你就是高中编程爱好者
作不到这点,(我想了很久,但我仍是不知道你适合去干什么。)
以上是我判断一个架构是否是好架构的标准,这是根据重要性来排列的。客户端架构跟服务端架构要考虑的问题和侧重点是有一些区别的。下面我会针对每一点详细讲解一下:
代码整齐,分类明确,没有common,没有core
代码整齐是每个工程师的基本素质,先不说你搞定这个问题的方案有多好,解决速度有多快,若是代码不整齐,一切都白搭。由于你的代码是要给别人看 的,你本身也要看。若是哪一天架构有修改,正好改到这个地方,你很容易本身都看不懂。另外,破窗理论提醒咱们,若是代码不整齐分类不明确,整个架构会随着 一次一次的拓展而愈来愈混乱。
分类明确的字面意思你们必定都了解,但还有一个另外的意思,那就是:不要让一个类或者一个模块作两种不一样的事情
。若是有类或某模块作了两种不一样的事情,一方面不适合将来拓展,另外一方面也会形成分类困难。
不要搞Common,Core这些东西。每家公司的架构代码库里面,最恶心的必定是这两个名字命名的文件夹,我这么说必定不会错。不要开 Common,Core这样的文件夹,开了以后后来者必定会把这个地方搞得一团糟,最终变成Common也不Common,Core也不Core。要记 住,架构是不断成长的,是会不断变化的。不是每次成长每次变化,都是由你去实现的。若是真有什么东西特别小,那就索性为了他单独开辟一个模块就行了,小就 小点,关键是要有序。
不用文档,或不多文档,就能让业务方上手
谁特么会去看文档啊,业务方他们已经被产品经理逼得很忙了。因此你要尽量让你的API名字可读性强,对于iOS来讲,objc这门语言的特性把这个作到了极致,函数名长就长一点,没关系。
好的函数名:
- (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。另外,若是是高度模块化的架构,拓展起来将会是一件很是容易的事情。
保持必定量的超前性
这一点能看出架构师是否关注行业动态,是否能准确把握技术走向。保持适度的技术上的超前性,可以使得你的架构更新变得相对轻松。
另外,这里的超前性也不光是技术上的,还有产品上的。谁说架构师就不须要跟产品经理打交道了,没事多跟产品经理聊聊天,听听他对产品将来走向的畅 想,你就能够在合理的地方为他的畅想留一条路子。同时,在创业公司的环境下,不少产品需求其实只是为了赶产品进度而产生的妥协方案,最后仍是会转到正轨 的。这时候业务方能够不实现转到正规的方案,可是架构这边,是必定要为这种可预知的改变作准备的。
接口少,接口参数少
越少的接口越少的参数,就能越下降业务方的使用成本。固然,充要条件仍是要知足的,如何在知足充要条件的状况下尽量地减小接口和参数数量,这就能看出架构师的功力有多深厚了。
高性能
为何高性能排在最后一位?
高性能很是重要,可是在客户端架构中,它不是第一考虑因素。缘由有下:
可是!不重要不表明用不着去作,关于性能优化的东西,我会对应放到各系列文章里面去。好比网络层优化,那就会在网络层方案的那篇文章里面去写,对应每层架构都有每层架构的不一样优化方案,我都会在各自文章里面一一细说。
昨晚上志豪看了这篇文章以后说,看到你这个题目原本我是指望看到关于架构分层相关的东西的,可是你没写。
嗯,确实没写,当时没写的缘由是感受这个没什么好写的。前面谈论到架构的方法的时候,关于问题分类分模块这一步时,架构分层也属于这一部分,给我一笔带过了。
既然志豪提出来了这个问题,我想可能你们关于这个也会有一些想法和问题,那么我就在这儿讲讲吧。
其实分层这种东西,真没啥技术含量,全凭架构师的经验和素质。
咱们常见的分层架构,有三层架构的:展示层、业务层、数据层。也有四层架构的:展示层、业务层、网络层、本地数据层。这里说三层
、四层
,跟TCP/IP所谓的五层或者七层不是同一种概念。再具体说就是:你这个架构在逻辑上是几层那就几层,具体每一层叫什么,作什么,没有特定的规范。这主要是针对模块分类而言的。
也有说MVC架构,MVVM架构的,这种层次划分,主要是针对数据流动的方向而言的。
在实际状况中,针对数据流动方向作的设计和针对模块分类作的设计是会放在一块儿的,也就是说,一个MVC架构能够是四层:展示层、业务层、网络层、本地数据层。
那么,为何我要说这个?
大概在五六年前,业界很流行三层架构
这个术语。而后各类文档资料漫天的三层架构
,而且喜欢把它与MVC
放在一块儿说,MVC三层架构/三层架构MVC
,以致于不少人就会认为三层架构
就是MVC
,MVC
就是三层架构
。其实不是的。三层架构
里面其实没有Controller
的概念,并且三层架构描述的侧重点是模块之间的逻辑关系。MVC
有Controller
的概念,它描述的侧重点在于数据流动方向。
好,为何流行起来的是
三层架构
,而不是四层架构
或五层架构
?
由于全部的模块角色只会有三种:数据管理者
、数据加工者
、数据展现者
,意思也就是,笼统说来,软件只会有三层,每一层扮演一个角色。其余的第四层第五层,通常都是这三层里面的其中之一分出来的,最后都能概括进这三层的某一层中去,因此用三层架构
来描述就比较广泛。
那么咱们怎么作分层?
应该如何作分层,不是在作架构的时候一开始就考虑的问题。虽然咱们要按照自顶向下的设计方式来设计架构,可是通常状况下不适合直接从三层开始。通常 都是先肯定全部要解决的问题,先肯定都有哪些模块,而后再基于这些模块再往下细化设计。而后再把这些列出来的问题和模块作好分类。分类以后不出意外大多数 都是三层。若是发现某一层特别庞大,那就能够再拆开来变成四层,变成五层。
举个例子:你要设计一个即时通信的服务端架构,怎么分层?
记住,不要一上来就把三层架构
的规范套上去,这样作是作不出好架构的。
你要先肯定都须要解决哪些问题。这里只是举例子,我随意列出一点意思意思就行了:
解决第一个问题须要一个连接管理模块,连接管理模块通常是经过连接池来实现。 解决第二个问题须要有一个数据交换模块,从A接收来的数据要给到B,这个事情由这个模块来作。 解决第三个问题须要有个数据库,若是是服务于大量用户,那么就须要一个缓冲区,只有当须要存储的数据达到必定量时才执行写操做。 解决第四个问题能够有几种解决方案,一个是集群中有那么几台服务器做为寻路服务器,全部寻路的服务交给那几台去作,那么你须要开发一个寻路服务的 Daemon。或者用广播方式寻路,但若是寻路频次很是高,会形成集群内部网络负载特别大。这是你要权衡的地方,目前流行的思路是去中心化,那么要解决网 络负载的问题,你就能够考虑配置一个缓存。
因而咱们有了这些模块:
连接管理、数据交换、数据库及其配套模块、寻路模块
作到这里还远远没有结束,你要继续针对这四个模块继续往下细分,直到足够小为止。可是这里只是举例子,因此就不往下深究了。
另外,我要提醒你的是,直到这时,仍是跟几层架构毫无关系的。当你把全部模块都找出来以后,就要开始整理你的这些模块,颇有可能架构图就是这样:
连接管理 收发数据 收发数据 数据交换 / \ \ 连接管理 数据交换 寻路服务 ========\ / \ ========/ 数据库服务 寻路服务 数据库服务 /
而后这些模块分完以后你看一下图,嗯,一、二、3,一共三层,因此那就是三层架构
啦。在这里最消耗脑力最考验架构师功力的地方就在于:找到全部须要的模块
, 把模块放在该放的地方
这个例子侧重点在于如何分层,性能优化、数据交互规范和包协议、数据采集等其余一系列必要的东西都没有放进去,但看到这里,相信你应该了解架构师是怎么对待分层问题的了吧?
对的,答案就是没有分层。所谓的分层都是出架构图以后的事情了。因此你看别的架构师在演讲的时候,上来第一句话差很少都是:"这个架构分为如下几层..."。但考虑分层的问题的时机绝对不是一开始就考虑的。另外,模块必定要把它设计得独立性强,这实际上是门艺术活。
另外,这虽然是服务端架构,可是思路跟客户端架构是同样的,侧重点不一样罢了。之因此不拿客户端架构举例子,是由于这方面的客户端架构苹果已经帮你作好了绝大部分事情,没剩下什么值得说的了。
评论区MatrixHero提到一点:
关于common文件夹的问题,仅仅是文件夹而已,别无他意。若是后期维护出了代码混乱多是由于,和服务器沟通协议不统一,或代码review不及时。应该有专人维护公共类。
这是针对我前面提出的不要Common,不要Core
而言的,为何我建议你们不要开Common文件夹?我打算分几种状况给你们解释一下。
通常状况下,咱们都会有一些属于这个项目的公共类,好比取定位坐标,好比图像处理。这些模块可能很是小,就h和m两个文件。单独拎出来成为一个模块 感受未入流,可是又不属于其余任何一个模块。因而你们颇有可能就会把它们放入Common里面,我目前见到的大多数工程和大多数文档里面的代码都喜欢这么 作。在当时来看,这么作看不出什么问题,但关键在于:软件是有生命,会成长的
。当时分出来的小模块,颇有可能会随着业务的成长,逐渐发展成大模块,发展成大模块后,能够再把它从Common移出来单独成立一个模块。这个在理论上是没有任何问题的,然而在实际操做过程当中,工程师在拓张这个小模块的时候,不太容易会去考虑横向依赖
的问题,由于当时这些模块都在Common里面,直接进行互相依赖是很是符合直觉的,并且也不算是不遵照规范。然而要注意的是,这才是Commom代码混乱
的罪魁祸首,Common文件夹纵容了不精心管理依赖
的作法。当Common里面的模块依赖关系变得复杂,再想要移出来单独成立一个模块,就不是当初设置Common时想的等规模大了再移除也不迟
那么简单了。
另外,
Common
有的时候也不只仅是一个文件夹。
在使用Cocoapods来管理项目库的时候,Common
每每就是一个pod。这个pod里面会有A/B/C/D/E这些函数集或小模块。若是要新开一个app或者Demo,势必会使用到Common这个pod,这么作,每每会把不须要包含的代码也包含进去,我对项目有高度洁癖,这种状况会让我以为很是不舒服。
举个例子:早年安居客的app还不是集齐全部新房
、二手房
、租房
业务的。当你刚开始写新房
这个app的时候,建立了一个Common这个pod,这里面包含了一些对于新房
来讲比较Common的代码,也包含了对于这个app来讲比较Common的代码。过了半年或者一年,你要开始二手房
这个app,我以为大多数人都会选择让二手房
也包含这个Common,因而这个Common颇有可能本身走上另外一条发展的道路。等到了租房
这个业务要开app的时候,Common已经很是之庞大,相信这时候的你也不会去想整理Common的事情了,先把租房
搞定,因而Common最终就变成了一坨屎。
就对于上面的例子来讲,还有一个要考虑的是,分出来的三个业务颇有可能会有三个Common,假设三个Common里面都有公共的功能,交给了三个 团队去打理,若是遇到某个子模块须要升级,那么三个Common里面的这个子模块都要去同步升级,这是个很不效率的事情。另外,颇有可能三个Common 到最后发展成彼此不兼容,可是代码类似度很是之高,这个在架构上,是属于分类条理不清
。
就在去年年中的时候,安居客决定将三个业务归并到同一个App。好了,若是你是架构师,面对这三个Common,你打算怎么办?要想最快出成果,那 就只好忍受代码冗余,赶忙先把架子搭起来再说,不然你面对的就是剪不断理还乱的Common。此时Common就已经很无奈地变成一坨屎了。这样的 Common,你本身说不定也搞不清楚它里面到底都有些什么了,交给任何一我的去打理,他都不敢作完全的整理的。
还有就是,Common自己就是一个粒度很是大的模块。在阿里这样大规模的团队中,即使新开一个业务,都须要在整个app的环境下开发,为何?因 为模块拆分粒度不够,要想开一个新业务,必须把其余业务的代码以及依赖所有拉下来,而后再开新入口,你的新业务才能进行正常的代码编写和调试。然而你的新 业务其实只依赖首页入口、网络库等这几个小模块,不须要依赖其余那么多的跟你不要紧的业务。如今每次打开天猫的项目,我都要等个两三分钟,这很是之蛋疼。
可是你们真的不知道这个缘由吗?知道了这个缘由,为何没人去把这些粒度不够细的模块整理好?在我看来,这件事没人敢作。
花这么大的成本只是为了减小开启项目时候等待IDE打开时的那几分钟时间?我想若是我是你leader,我也应该不会批准你作这样的事情的。因此, 与其到了后面吃这个苦头,不如一开始作架构的时候就不要设置Common,到后面就能省力不少。架构师的工做为何是功在当代利在千秋,架构师的素质为什 么对团队这么重要?我以为这里就是一个最好的体现。
简而言之,不建议开Common的缘由以下:
那么,不设Common会带来哪些好处?
Common的好处只有一个,就是前期特别省事儿。然而它的坏处比好处要多太多。不设置Common,再小的模块再小的代码也单独拎出来,最多就是 Podfile里面要多写几行,多写几行最多只花费几分钟。但若要消除Common所带来的罪孽,不是这几分钟就能搞定的事情。既然不用Common的好 处这么多,那何乐而不为呢?
假设未来你的项目中有一个类是用来作Location的,哪怕只有两个文件,也给他开一个模块就叫Location。若是你的项目中有一个类是用来 作ImageProcess的,那也开一个模块就叫ImageProcess。不要都放到Common里面去,未来你再开新的项目或者新的业务,用 Location就写依赖Location,用ImageProcess就写依赖ImageProcess,不要再依赖Common了,这样你的项目也好 管理,管理Common的那我的日子过得也轻松(这我的其实均可以不须要了,把他的工资加到你头上不是更好?:D),未来要升级,顾虑也少。
一会儿挖了个大坑,在开篇里扯了一些淡。
嗯,干货会在后续的系列文章里面扑面而来的!