iOS应用架构随笔 (一) 概览
前言
感谢缺总无私分享的技术博客,拖了好几天终于在今天决定花一些时间好好拜读一下,按照缺总的再三叮嘱 一定要从开篇开始看啊!,看的同时打开另一台电脑跑这来敲敲键盘加深一下记忆……
感觉这个题目还是起的太大了我这样的菜鸡讨论架构不是找抽的么,但反正也没打算投稿这句话为何这么熟悉……
,只是当做读书笔记一样写一点自己对架构这方面的理解,如果有人看到这篇文章,郑重提醒您看到这里的时候就可以点击网页上面的x号了。
说的好像会有人过来看一样……
感谢Casa Taloyum 的技术博客,受益良多。
一、什么是iOS架构
按照字面上的意思,当前移动端app翻来覆去所做的,就是下面这张图中所做的事。
网络架构-通俗说法.png
根据上图我自己做的总结:
-> 1.写好了界面 -> 2. 调用网络请求 ->3. 根据返回的数据展示列表
-> 4.发生一些用户交互 ->5. 展示具体的单页给用户 -> 6. 发生交互
-> 7. 更多的单页或者列表 -> 8. 返回列表 -> 9. 再来一次
虽然每个app都有自己独特的表现手法,但干的事情真的都是上面这些事情。
所以……架构师的活干完了?
当然不是!要是这样岂不是人人都是架构师了? 但人人都是产品经理!
app的确是在做这些事,不过支撑这些的基础,肯定不像上面这么简单。
那么架构到底要干些什么呢??
主要应该还是下面这几件事:
(1). 针对app
- 调用网络API
如何让业务开发工程师方便安全地调用网络API?然后尽可能保证用户在各种网络环境下都能有良好的体验?
-
页面展示
页面如何组织,才能尽可能降低业务方代码的耦合度?尽可能降低业务方开发界面的复杂度,提高他们的效率? -
数据的本地持久化
当数据有在本地存取的需求的时候,如何能够保证数据在本地的合理安排?如何尽可能地减小性能消耗? -
动态部署方案
iOS应用有审核周期,如何能够通过不发版本的方式展示新的内容给用户?如何修复紧急bug?
(2). 针对开发团队
1.收集用户数据,给产品和运营提供参考
2.合理地组织各业务方开发的业务模块,以及相关基础模块
3.每日app的自动打包,提供给QA工程师的测试工具
二、架构设计的方法
万事开头难,迭代一款产品永远比从0开始简单 转载永远比原创简单……,一万个人心中有一万个哈姆雷特,每位资深架构师肯定也是对“架构”这个行为有自己独特的理解。但是就像上面所说的app的的确确都是在翻来覆去做这些事
一样,架构设计也有它的中心思想,概括下来下面这几点一定贯穿其中:
1 . 拥有优秀的全局观
2 . 高质量的代码 (优质的代码审美能力)
3 . **灵活的使用各种设计模式 **
再往下细分:
Step 1. 搞清楚要解决哪些问题,并找到解决这些问题的充要条件
举个栗子!.png① 搞明白自己到底需要什么,不为了架构而架构。
举例: 如果项目已经使用了MVC模式,能够应对现有的需求,且不存在什么重大缺陷。但是现在MVVM很流行,那么就不值得为了赶潮流或者说为了提升那一点点的性能而将MVC推翻上MVVM,得不偿失,还容易埋下隐患。
② 何谓解决问题的充要条件 ,充要条件能做什么?
首先 **充要条件 == 充分必要条件 **
仅仅针对 我自己所理解的…… iOS而言,所谓充要条件,就是各个文件之间沟通的桥梁,再直白瞎逼逼……一点就是其具体表现为各种 import "XXXXX.h"
, 因为import .m文件基本上来说是不允许的,那么.h文件就是iOS中的载具,调取需要的属性或者方法来搭建项目。
但是 总有许多但是……
, 无论是cocoa提供的API,还是coder自己写出来的东西,他并不一定都满足 “充分必要” 这四个字,有些可能是累赘,有些可能根本不应该暴露在.h文件中,有些可能有用但是不一定会被使用,诸如此类。
代码之间通信需要通道,仅仅就通道而言,其数量越少 (传递的参数越少) ,代码的耦合度相对就越低,而解耦所带来的好处是显而易见的 ----> 封装好的代码可以直接拿来用、升级\替换模块更方便、-----> 代码更美观,更实用,发生灾难性bug的几率越低。
所谓充要,应该是精简 + 实用 的综合体,从架构角度来说,充要条件应该是能精简就精简,这决定了整个架构是否易用。
Step 2. 问题分类、分模块
所谓模块化 ---> 使得代码便于管理、解耦、提升编译速度
这个步骤是在Step 1.的基础上进行的,确定了主体设计模式,比如选用了MVC模式,那么接下来的工作至少有头绪了,是按照页面位置还是按照功能来细分模块就随意了。
Step 3. 搞清楚各问题之间的依赖关系,建立好模块交流规范并设计模块
总结起来就是一句话 建立起一套统一的交流规范,关键词是一套 和 统一 。
假设拥有两套甚至更多的交流规范,无疑是在给交流设置人为障碍,五花八门的交流规范用起来可能的确会非常的酷炫,但首先这有炫技的成分在里面,另外带来的直接后果很可能这玩意就只有作者自己才能玩的转了。
Step 4. 推演预测一下未来可能的走向,必要时添加新的模块,记录更多的基础数据以备未来之需
这里又滚回上面所说的中心思想:拥有优秀的全局观 ,一个优秀的架构师可以用优秀的架构来赋予app灵魂,但架构这玩意吧……没有最好只有更好!
,很多时候一次迭代并不能把架构师挖的坑都填上事实上如果一次填满了感觉更可怕……
,那么,完成一轮架构之后,为未来可能出现的情况做一些记录脑补,下一次的时候就更游刃有余了。
Step 5. 先解决依赖关系中最基础的问题,实现基础模块,然后再用基础模块堆叠出整个架构
验证架构是否合理的阶段 ----> 大模块也是由基础模块堆砌而成的,大模块堆砌成整个架构 。
其实真实的开发过程中,一条龙顺到底的情况少之又少,更多的是在不停的改需求 产品经理躺枪! _(:з」∠)_
,但越是如此,越是要更加认真的去应对,最好是达到吹毛求疵的地步,一旦发现架构有问题,要立刻进行调整。
Step 6. 打点,跑单元测试,跑性能测试,根据数据去优化对应的地方
就是字面上的意思,我还停留在靠全局断点找bug的阶段,性能什么的还是先不讨论了,手里掌握着各单元的具体测试结果,各个模块的优化方案,无论是面向BOSS邀功,还是面向小弟分配任务,都可以做的更好。
三、什么样的架构才是好的架构?
这块还随笔个鸡毛啊,我要是总结的出来我还犯得着整天焦虑找不到工作么,直接复制粘贴吧……
1. 代码整齐,分类明确,没有common,没有core
2. 不用文档,或很少文档,就能让业务方上手
3. 思路和方法要统一,尽量不要多元
4. 没有横向依赖,万不得已不出现跨层访问
5. 对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件
6. 易测试,易拓展
7. 保持一定量的超前性
8. 接口少,接口参数少
9. 高性能
然后是一条一条的详解
1 . 代码整齐,分类明确,没有common,没有core
代码整齐:首先牢记一点,你的代码不但是给电脑看的,也是给别人看的,不要让别人看到你的东西就一头雾水,甚至当自己需要修改架构中的某处时,自己面对纷乱的代码也无从下手。
分类明确:不要让一个类或者一个模块做两种不同的事情,如果有类或某模块做了两种不同的事情,一方面不适合未来拓展,另一方面也会造成分类困难。
如果说上面还能勉强知道是怎么回事的话,这段就是根本不知道在写什么东西了,
common: 共同/通用模块
core: 核心模块
首先这两个东西都是很纠结的玩意,据说大一点的公司项目都有这两个东西。但是以这两个名字命名的文件夹的共同点就是都很恶心。按照原文的说法,我的理解大概是这样的: 这两个东西都是在无形中给整个项目加了条条框框,因为既然是“核心 & 通用”嘛,。但是文件夹是死的,架构是活的,码农自己都不敢说以后就不会加入新的common或者core,长此以往,往往这两个东西会变的既不common也不core,这就不仅仅是有碍观瞻这么简单的问题了,所以应该尽量避免出现这两个东西。
解决方案:别去设置什么common core ,如果真需要添加什么功能什么模块,理清思路之后,直接添加就好了,哪怕它很小 --> 小就小点,但是一定要有序。
2 . 不用文档,或很少文档,就能让业务方上手
先搬运下原博上的代码片段
好的函数名:
- (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这个参数根本不是这个函数要解决的问题的充要条件,如果你发现你不得不这么做,
那一定是架构有问题。
并不是说让你从此不用文档,而是让业务方不通过、或者少通过文档就能快速的上手!
绝大多数的时候,业务方(码农)都是在和产品经理拿着板砖互砸,撕……撕的不可开交的时候,架构师这时候发一份写的云里雾里的文档过去,码农多半是不会在这么着急上火的时候选择仔细研读你的文档的。
那咋整,我辛辛苦苦写出来的文档没人看,我好委屈!
解决方法也不难:“尽量让你的API的名字可读性变强即可”,就像上面代码里面那种,一眼望过久就知道是在指定位置替换图片的。而针对iOS或者说Objective-C而言,这方面我们有先天的优势 是的,我们的API本来就很长!
题外话,混迹于码农群,前段后端移动端嵌入式什么都有,偶尔会碰到几个其他语言转iOS的,无一例外的都说了这么一句话 Cocoa的API真是又臭又长 这算客气的,更多的是“苹果傻逼”。我没接触过什么其他语言,但我觉得能让这么多人异口同声的说出这种话,应该还是有道理的……
3 . 思路和方法要统一,尽量不要多元
// 继续搬运……
解决一个问题会有很多种方案,但是一旦确定了一种方案,就不要在另一个地方采用别的方案了。
也就是做架构的时候,你得时刻记住当初你决定要处理这样类型的问题的方案是什么,以及你的
初衷是什么,不要摇摆不定。
另外,你当初设立这个模块一定是有想法有原因的,要记录下你的解决思路,不要到时候换个地方
你又灵光一现啥的,引入了其他方案,从而导致异构。
要是一个框架里面解决同一种类似的问题有各种五花八门的方法或者类,我觉得做这个架构的架构
师一定是自己都没想清楚就开始搞了。 // 那就不是架构师了是么……
上面这段我目前只能理解到“尽量使用一种思路去写代码“这个层次,惭愧。
4 .没有横向依赖,万不得已不出现跨层访问
// 继续搬,估计离看懂还有很长一段时间
没有横向依赖是很重要的,这决定了你将来要对这个架构做修补所需要的成本有多大。
要做到没有横向依赖,这是很考验架构师的模块分类能力和是否熟悉业务的。
跨层访问是指数据流向了跟自己没有对接关系的模块。有的时候跨层访问是不可避免的,
比如网络底层里面信号从2G变成了3G变成了4G,这是有可能需要跨层通知到View的。
但这种情况不多,一旦出现就要想尽一切办法在本层搞定或者交给上层或者下层搞定,
尽量不要出现跨层的情况。跨层访问同样也会增加耦合度,当某一层需要整体替换的时候,
牵涉面就会很大。
5 .对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件
// 如果上面4那段还能算有点理解的话……这里就是天书了,仿佛间看到了张靓颖在我眼前微笑……
把这点做好,很依赖于架构师的经验。架构师必须要有能力区分哪些情况需要限制灵活性,
哪些情况需要创造灵活性。比如对于Core Data技术栈来说,ManagedObject理论上是
可以出现在任何地方的,那就意味着任何地方都可以修改ManagedObject,这就导致
ManagedObjectContext在同步修改的时候把各种不同来源的修改同步进去。这时候就
需要限制灵活性,只对外公开一个修改接口,不暴露任何ManagedObject在外面。
如果是设计一个ABTest相关的API的时候,我们又希望增加它的灵活性。使得业务方不光
可以通过Target-Action的模式实现ABtest,
也要可以通过Block的方式实现ABTest,要尽可能满足灵活性,减少业务方的使用成本。
6 . 易测试易拓展
老生常谈,要实现易测试易拓展,那就要提高模块化程度,尽可能减少依赖关系,便于mock。
另外,如果是高度模块化的架构,拓展起来将会是一件非常容易的事情。
7 . 保持一定量的超前性
这一点能看出架构师是否关注行业动态,是否能准确把握技术走向。保持适度的技术上的超前性,
能够使得你的架构更新变得相对轻松。
另外,这里的超前性也不光是技术上的,还有产品上的。谁说架构师就不需要跟产品经理打交道了,
没事多跟产品经理聊聊天,听听他对产品未来走向的畅想,你就可以在合理的地方为他的畅想留
一条路子。同时,在创业公司的环境下,很多产品需求其实只是为了赶产品进度而产生的妥协方案,最后还是会转到正轨的。这时候业务方可以不实现转到正规的方案,
但是架构这边,是一定要为这种可预知的改变做准备的。
8 . 接口少,接口参数少
越少的接口越少的参数,就能越降低业务方的使用成本。当然,充要条件还是要满足的,
如何在满足充要条件的情况下尽可能地减少接口和参数数量,这就能看出架构师的功力有多深厚了。
又回到了原来阐述的问题 -> 充要条件上。
9 . 高性能
高性能非常重要,但是在客户端架构中,它不是第一考虑因素。
原因有下:
1. 客户端业务变化非常之快,做架构时首要考虑因素应当是便于业务方快速满足产品需求,
因此需要尽可能提供简单易用效果好的接口给业务方,而不是提供高性能的接口给业务方。
2. 苹果平台的性能非常之棒,正常情况下很少会出现由于性能不够导致的用户体验问题。
3. 苹果平台的优化手段相对有限,甚至于有些时候即便动用了无所不用其极的手段乃至不择手段
牺牲了稳定性,性能提高很有可能也只不过是100ms到90ms的差距。10%的性能提升对于服务端
来说很不错了,因为服务端动不动就是几十万上百万的访问量,几十万上百万个10ms是很可观的。
但是对于客户端的用户来说,他无法感知这10ms的差别,如果从10s优化成9s用户还是有一定感知的,
但是100ms变90ms,我觉得吧,还是别折腾了。
看过一篇文章,讲现下多种JSON解析工具的,比如YYModel 、MJExtension 、EasyJson 等,测试其解析一个巨大的JSON的效率,最终结果显示YYModel消耗时间至少比其他少20%左右。YYModel的GitHub上也有相关介绍文章,有空放上链接
就像上面说的那样,在服务端,如果能提升个20%的效率,那是不得了的事。但是在移动端,20%往往也就是10~100ms不等,比蝴蝶振一次翅的时间还少,这个时间 人类 往往是感觉不出来的你好,我叫快银……
,那么就不值得为了这微小的性能提升去瞎折腾整个项目。
四、为项目分层
那么就项目来说,怎么分层? 有几层?
常见的分层架构,有三层架构的:展现层、业务层、数据层。也有四层架构的:展现层、业务层、网络层、本地数据层。
注意这里的三层 四层 跟TCP/IP 有七层 不是一个概念。
而针对iOS而言,我们挂在嘴边的往往是 MVC架构,MVVM架构这些,但这种层次划分,主要是针对数据流动的方向而言的。跟Model , View ,Controller 这三者特别是Controller的关系并不大
换句话说:我之前所理解的,MVC就是Model-View-Controller这三层结构这句话从根子上就是错误的。架构分层从来就没有controller什么事,同理,MVVM中,model和controller之间的黏合剂什么的跟分层也没啥关系。
看看大牛是怎么说的:
应该如何做分层,不是在做架构的时候一开始就考虑的问题。虽然我们要按照自顶向下的设计方式
来设计架构,但是一般情况下不适合直接从三层开始。
一般都是先确定所有要解决的问题,先确定都有哪些模块,然后再基于这些模块再往下细化设计。
然后再把这些列出来的问题和模块做好分类。分类之后不出意外大多数都是三层。如果发现某一层
特别庞大,那就可以再拆开来变成四层,变成五层。
1.jpg
我还是先别纠结架构师操心的活了,先挑能看得懂的看吧……