缘由
从事iOS工作一年多了,主要从事QQ钱包SDK开发和财付通app维护,随着对业务的慢慢熟悉,最近在思考这两款应用架构设计的思想,刚好昨天在微信里看了一篇iOS大牛对终端应用架构的分享,乘热打铁,下面浅谈下我对ios应用架构设计的理解,写的不好或不对的地方,欢迎大家拍砖,我们一起来探讨。
假如问你一个iOS or Android app的架构,你会从哪些方面来说呢?
不要急着给出你的答案,可以先在你的脑子里思考3分钟,再看下面我要讲的内容。
其实对于iOS客户端应用的架构来说,复杂度不亚于服务端,但侧重点和入手点却跟服务端不太一样。比如客户端应用就不需要考虑类似服务端大并发量的问题,正常的app就根本不需要考虑。因为我个人做Android的时间不长,如果你是Android开发者,你可以侧重看我提出的一些架构思想,毕竟不管做什么,思路是相通的,实现手段不同罢了。
当我们讨论客户端应用架构的时候,我们在讨论什么?
其实市面上大部分应用不外乎就是颠过来倒过去地做以下这些事情:
简单来说就是调API,展示页面,然后跳转到别的地方再调API,再展示页面。
App确实就是主要做这些事情,但是支撑这些事情的基础,就是做架构要考虑的事情。
- 调用网络API
- 页面展示
- 数据的本地持久化
- 动态部署方案
上面这四大点,稍微细说一下就是:
- 如何让业务开发工程师方便安全地调用网络API?然后尽可能保证用户在各种网络环境下都能有良好的体验?
- 页面如何组织,才能尽可能降低业务方代码的耦合度?尽可能降低业务方开发界面的复杂度,提高他们的效率?
- 当数据有在本地存取的需求的时候,如何能够保证数据在本地的合理安排?如何尽可能地减小性能消耗?
- iOS应用有审核周期,如何能够通过不发版本的方式展示新的内容给用户?如何修复紧急bug?
上面几点是针对App说的,下面还有一些是针对团队说的:
- 收集用户数据,给产品和运营提供参考
- 合理地组织各业务方开发的业务模块,以及相关基础模块
- daily build 自动打包,提供给产品体验,给测试观测性能
所以当我们讨论客户端应用架构的时候,我们讨论的差不多就是这些问题。
架构设计的方法
所有事情最难的时候都是开始做的时候,当你开始着手设计并实现某一层的架构乃至整个app的架构的时候,很有可能会出现暂时的无从下手的情况。不管你采用什么方法,全局观、高度的代码审美能力、灵活使用各种设计模式一定都是贯穿其中的。
第一步:搞清楚要解决哪些问题,并找到解决这些问题的充要条件
你必须得清楚你要做什么,业务方希望要什么。而不是为了架构而架构,也不是为了体验新技术而改架构方案。以前是MVC,最近流行MVVM,如果过去的MVC是个好架构,没什么特别大的缺陷,就不要推倒然后搞成MVVM。
搞清楚对于业务方而言的真正充要条件很重要!这决定了你的架构是否足够易用。另外,传的参数越少,耦合度相对而言就越小,你替换模块或者升级模块所花的的代价就越小。
第二步:问题分类,分模块
这个不用多说了吧。
第三步:搞清楚各问题之间的依赖关系,建立好模块交流规范并设计模块
关键在于建立一套统一的交流规范。要注意的是,一定是建立一套统一的交流规范,不是两套,不是多套。你要坚持你的价值观,不要摇摆不定。要是搞出各种五花八门的规范出来,一方面有不切实际的炫技嫌疑,另一方面也会带来后续维护的灾难。
第四步:推演预测一下未来可能的走向,必要时添加新的模块,记录更多的基础数据以备未来之需
很多称职的架构师都会在这时候考虑架构未来的走向,以及考虑做完这一轮架构之后,接下来要做的事情。一个好的架构虽然是功在当代利在千秋的工程,但绝对不是一个一劳永逸的工程。软件是有生命的,你做出来的架构决定了这个软件它这一生是坎坷还是幸福。
第五步:先解决依赖关系中最基础的问题,实现基础模块,然后再用基础模块堆叠出整个架构
这一步也是验证你之前的设计是否合理的一步,随着这一步的推进,你很有可能会遇到需要对架构进行调整的情况。这个阶段一定要吹毛求疵高度负责地去开发,不要得过且过,发现架构有问题就及时调整。否则以后调整的成本就非常之大了。
第六步:打点,跑单元测试,跑性能测试,根据数据去优化对应的地方
你得用这些数据去向你的leader邀功,你也得用这些数据去不断调整你的架构。
总而言之就是要遵循这些原则:自顶向下设计(1,2,3,4步),自底向上实现(5),先测量,后优化(6)。
什么样的架构是好架构?
- 代码整齐,分类明确
- 不用文档,或很少文档,就能让业务方上手
- 思路和方法要统一,尽量不要多元
- 没有横向依赖,万不得已不出现跨层访问
- 对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件
- 易测试,易拓展
- 保持一定量的超前性
- 接口少,接口参数少
- 高性能
下面针对每一点详细讲解一下:
代码整齐,分类明确,没有common,没有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。另外,如果是高度模块化的架构,拓展起来将会是一件非常容易的事情。
保持一定量的超前性
保持适度的技术上的超前性,能够使得你的架构更新变得相对轻松。
另外,这里的超前性也不光是技术上的,还有产品上的。谁说架构师就不需要跟产品经理打交道了,没事多跟产品经理聊聊天,听听他对产品未来走向的畅想,你就可以在合理的地方为他的畅想留一条路子。
接口少,接口参数少
越少的接口越少的参数,就能越降低业务方的使用成本。当然,充要条件还是要满足的,如何在满足充要条件的情况下尽可能地减少接口和参数数量。
高性能
为什么高性能排在最后一位?
高性能非常重要,但是在客户端架构中,它不是第一考虑因素。原因有下:
- 客户端业务变化非常之快,做架构时首要考虑因素应当是便于业务方快速满足产品需求,因此需要尽可能提供简单易用效果好的接口给业务方,而不是提供高性能的接口给业务方。
- 苹果平台的性能非常之棒,正常情况下很少会出现由于性能不够导致的用户体验问题。
- 苹果平台的优化手段相对有限,甚至于有些时候即便动用了无所不用其极的手段乃至不择手段牺牲了稳定性,性能提高很有可能也只不过是100ms到90ms的差距。10%的性能提升对于服务端来说很不错了,因为服务端动不动就是几十万上百万的访问量,几十万上百万个10ms是很可观的。但是对于客户端的用户来说,他无法感知这10ms的差别,如果从10s优化成9s用户还是有一定感知的,但是100ms变90ms,我觉得吧,还是别折腾了。
但是!不重要不代表用不着去做,关于性能优化这里有很多学问,需要在工作中慢慢积累。
尾声
看了上面的内容,再问一遍自己假如问有一个iOS or Android app的架构,会从哪些方面来思考呢?