安居客iOS应用架构第二版的原因大部分是我做的,期间总结了一些经验。将近一年后,前同事zzz在微信朋友圈发了一个问题:如果问你一个iOS或者Android应用的架构,你会怎么说?看到这个问题的时候正好在坐公交回家的路上,所以无聊的时候就回答了。zzz在微信朋友圈问了几个问题后,觉得有必要开个博客分享一下个人的看法。其实对于iOS客户端应用的架构来说,其复杂度不亚于服务端,只是侧重点和出发点与服务端不一样。比如客户端应用不需要考虑像C10K这样的问题,普通的app根本不需要考虑。在本系列文章中,我将主要关注iOS应用程序架构。很多解决方案也是基于iOS技术栈的特点。因为我个人不是很喜欢写Java,所以对Android不是很了解。如果你是Android开发者,你可以关注我提出的一些架构思想。毕竟不管做什么,思路都是一样的,实现的方法是不一样的。当我们谈论客户端应用架构时,我们在谈论什么?其实市面上的大部分应用无非就是把下面这些事情倒过来做:简单来说就是调整API,显示页面,然后跳转到其他地方调整API,再显示这一页。那么这是一个多么好的结构呢?不,不。----包不一样《天龙八部》App确实主要是做这些东西,但是支持这些东西的基础是架构需要考虑的东西。本地持久化动态部署方案调用网络API页面展示数据以上四点,稍微细化一下就是:如何让业务开发工程师方便、安全的调用网络API?然后尽量保证用户在各种网络环境下都能有良好的体验?页面应该如何组织,尽量减少业务端代码的耦合?把业务方开发接口的复杂度降到最低,提高他们的效率?当需要在本地访问数据时,如何保证数据在本地的合理安排呢?如何尽可能降低性能消耗?iOS应用有审核周期,不发布版本怎么能给用户展示新内容呢?如何修复紧急错误?以上几点针对APP,以下针对团队:收集用户数据,为产品和运营提供参考,合理组织各业务方开发的业务模块,日常APP相关基础模块的自动打包,关于提供给QA工程师的测试工具,暂时只能想到以上三点。其实应该还有很多,就是想不起来了。因此,当我们谈论客户端应用程序架构时,我们几乎就是在谈论这些问题。本系列文章回答了哪些问题?本系列文章主要回答以下问题:网络层设计?网络层设计应该考虑哪些问题?在优化网络层时,我们应该从哪里入手呢?显示、调用和组织页面的设计选项有哪些?制定这些计划时应该考虑哪些问题?本地持久层有哪些设计方案?优缺点都有什么?不同方案应注意哪些问题?要实现动态部署,有哪些解决方案?不同解决方案的优缺点,它们的侧重点是什么?这篇文章回答了哪些问题?以上细分的四个问题,我会写在四篇文章中。那么这篇文章就是讲一些常识性的知识,也是给大家开个坑,讨论常识性的问题。架构设计的方法凡事最难的时候,就是你开始做的时候。当你开始设计实现某一层甚至整个app架构的架构时,很有可能会暂时无从下手。下面的方法论是我这些年总结出来的经验。每个架构师都必须有自己的方法论,但相同的是,不管你用什么方法,大局观、高代码审美能力、灵活运用各种设计模式都必须贯穿其中。欢迎在评论区讨论。第一步:弄清楚要解决哪些问题,找出解决这些问题的充分必要条件。你要清楚自己要做什么,业务方要什么。不是为了建筑而建造,也不是为了体验新技术而改变建筑。以前是MVC,最近流行MVVM。如果以前的MVC是一个很好的架构,没有什么大的缺陷,就不要推翻它,做成MVVM。我还想解释一下充分必要条件。有时系统提供的函数需要额外的参数,比如read函数。翻页时,当前页码也是充要条件。但是对于业务方来说,这些充要条件还可以进一步减少。例如,读取需要文件描述符、buf和大小。但是对于业务方来说,充分必要条件就是文件描述符足够了。另一个例子是翻页。事实上,业务方不需要记录当前页码。你给他暴露一个loadNextPage这样的方法就够了。搞清楚业务方真正的充要条件是非常重要的!这决定了你的架构是否易于使用。此外,传递的参数越少,耦合度越小,更换或升级模块的成本越低。第二步:问题分类,分模块就不用说了。第三步:找出问题之间的依赖关系,建立模块通信规范,进行模块设计。关键是要建立一套统一的沟通规范。这一步可以体现架构师在软件中的价值观。虽然有一定的优缺点(比如fatModel和thinModel),但由于都是架构师,基本上不会设计出明显不好的模型。程序的一部分,除非架构师不够合格。所以这里是架构师价值观输出的一个窗口,从中我们可以看出架构师的素质。另外需要注意的是,要建立一套统一的沟通规范,不是两套,不是多套。你要坚持自己的价值观,不能动摇。如果各种规格都生产出来,一方面会有不切实际的炫技之嫌,另一方面也会给后续的维修带来灾难。第四步:推导和预测未来可能的趋势,必要时增加新的模块,记录更多的基础数据以备将来需要。很多有能力的架构师会在这个时候考虑架构未来的方向,考虑完成这一轮的架构之后接下来要做什么。好的架构虽然是造福于未来的工程,但绝对不是一劳永逸的工程。软件是有生命的,你做的架构决定了软件的生命是坎坷还是幸福。第五步:首先解决依赖中最基本的问题,实现基本模块,然后使用基本模块堆叠整个架构。这一步也是验证你之前的设计是否合理的一步。随着这一步的进行,您可能会遇到需要调整架构的情况。这个阶段要本着高度负责和挑剔的态度进行开发,不要得过且过,架构出现问题要及时调整。否则,未来调整的成本将非常大。第六步:管理点,跑单元测试,跑性能测试,根据数据优化相应的地方。你要用这些数据为你的老板争光,你也要用这些数据不断调整你的结构。总而言之,要遵循这些原则:自上而下的设计(步骤1、2、3、4),自下而下的实施(5),先测量,再优化(6)。什么样的建筑师才是好建筑师?我每天都在学习,能很快的学习新技术和新思想,也能很快理解。如果你不能做到这一点,你就是一个编码员。商业背景,或至少对公司所在行业或公司业务非常熟悉。如果你做不到这一点,你就是运维。熟悉软件工程的各种规范,踩过无数坑。我们不会为了满足需求而做任何事情,也不提倡快速和肮脏。如果你做不到这一点,你更适合和你的竞争对手一起做工程师。及时承认错误,不要觉得承认错误会损害你作为架构师的身份。如果你做不到这一点,公关行业更适合你。你不能为了炫耀而炫耀自己的技能,并且你是一名高中编程爱好者。卓越做不到这一点,(我想了很久,还是不知道你适合做什么。)什么样的架构才是好的架构?代码整齐,分类清晰,没有通用的,没有核心的,没有文档的,或者文档很少的,让业务方上手的地方有局限性,灵活的地方要创造灵活的实施条件对于业务方面。易于测试,易于扩展,保持一定的超前量。接口少、接口参数少、性能高是我判断一个架构好坏的标准。按重要性顺序排列。客户端架构和服务器架构之间存在一些需要考虑和关注的差异。接下来,我将对每一点进行详细说明:代码整洁,分类清晰,无共性,无核心。整洁的代码是每个工程师的基本素质。先不说你解决这个问题的方法有多好,解决的速度有多快。如果代码不整洁,那一切都是徒劳的。因为你的代码是给别人看的,你自己也应该看。如果哪天架构有变化,正好改到这个地方,自己很容易看不懂。另外,破窗理论提醒我们,如果代码分类不整齐,分类不清晰,整个架构会随着每一次扩展而变得越来越混乱。clearclassification的字面意思大家肯定都明白,但是还有另外一个意思,那就是:不要让一个类或者一个模块做两件不同的事情。如果一个类或者一个模块做两件不同的事情,一方面不适合以后扩展,另一方面也会造成分类困难。不要搞Common,Core这些东西。在各个公司的架构代码库中,最恶心的一定是这两个名字命名的文件夹。我这么说不会错的。不要打开Common和Core等文件夹。开放之后,新人肯定会把这里搞得一团糟,最终变得普通不普通,核心不核心。请记住,架构在不断发展和变化。并非每一次成长和每一次改变都是你实现的。如果东西真的很小,那就干脆单独给他开一个模块,越小越小,关键是要有序。业务方面可以在没有任何或很少的文档的情况下开始。谁他妈的会阅读文档?业务端已经被产品经理忙得不可开交了。所以你必须让你的API名称尽可能的可读。对于iOS来说,objc语言的特性把这个做的很完美,函数名再长点也没关系。好的函数名:-(NSDictionary*)exifDataOfImage:(UIImage*)imageatIndexPath:(NSIndexPath*)indexPath;错误的函数名称:-(id)exifData:(UIImage*)imageposition:(id)indexPathcallback:(id)delegate;为什么不好?1.不要直接返回id或者传入id,那是不行的,用id比用id好。如果你连这个都做不到,那你得想想是不是你的架构有问题。2、通知业务方要传输什么,比如传输一张图片,写ofImage。如果要传position,就必须写IndexPath,不能写position这么笼统的东西。3.没有理由将委托作为参数传递,也不存在必须这样做的情况。而delegate这个参数根本就不是这个函数要解决的问题的充分必要条件。如果你发现非要这么做,那一定是架构有问题!思路和方法要统一,尽量不要多样化。一个问题有很多种解决方案,但是一旦你决定了一种解决方案,就不要在另一个地方使用另一种解决方案。也就是说,在做架构的时候,你要时刻记住你决定处理这类问题的方案是什么,你的初衷是什么,不要动摇。此外,您必须首先有一个想法和一个设置此模块的原因。一定要把自己的解法思路记录下来,不要灵光一闪什么的就换地方,引入其他的解法,造成异构。如果一个框架中有各种方法或者类来解决同一个相似的问题,我想做这个架构的架构师一定是没有想清楚就开始做的。没有横向依赖,跨层访问也不是万不得已。没有水平依赖很重要,这决定了你以后修复架构的成本是多少。做到无水平依赖,非常考验架构师的模块分类能力和对业务的熟悉程度。跨层访问是指数据流向与自身没有对接关系的模块。有时跨层访问是不可避免的。例如,网络底层的信号从2G到3G再到4G。这可能需要跨层通知View。但这种情况很少见。一旦发生,我们必须尽量在本层解决,或者交给上层或下层处理。尽量不要有跨层的情况。跨层访问也会增加耦合度。当某一层需要整体更换时,涉及的面积会非常大。业务方该限制的地方有限制,灵活的地方要为业务方实现灵活创造条件。做好这件事取决于架构师的经验。架构师必须能够区分限制灵活性的情况和创造灵活性的情况。比如对于CoreData技术栈来说,ManagedObject理论上可以出现在任何地方,也就是说ManagedObject可以在任何地方被修改,这就导致ManagedObjectContext在同步修改的时候需要同步各个来源的修改。这时候就需要限制灵活性,对外只暴露一个修改接口,不对外暴露任何ManagedObject。在设计一个ABTest相关的API时,我们希望增加它的灵活性。业务方既可以通过Target-Action方式实现ABTest,也可以通过分块方式实现ABTest,尽可能满足灵活性,降低业务方的使用成本。易于测试和易于扩展是老生常谈。要做到易测试、易扩展,就需要提高模块化程度,尽可能减少依赖,方便mocking。另外,如果是高度模块化的架构,扩展起来会非常容易。保持一定的先进性,可以看出架构师是否关注行业趋势,能否准确把握技术趋势。保持适度的技术进步可以使您的架构更新相对容易。此外,这里的进步不仅体现在技术上,更体现在产品上。谁说架构师不需要和产品经理打交道?没事找产品经理聊天,听听他对产品未来发展方向的想象。你可以在合理的地方给他想象空间。同时,在创业公司的环境中,很多产品需求其实只是为了赶上产品进度而做出的妥协,最终会走上正轨。此时,业务端不需要实施向正式解决方案的过渡,但在架构端,需要为这种可预见的变化做好准备。接口少,接口参数少接口少,参数少,业务方的使用成本越低。当然,充要条件还是要满足的。如何在满足充要条件的情况下,尽量减少接口和参数的数量,可见架构师的功力有多么深厚。高性能为什么高性能排在第一位?高性能非常重要,但在客户端架构中,它并不是唯一的考虑因素。原因如下:客户端业务变化非常快,在设计架构时首要考虑的应该是方便业务方快速满足产品需求。因此,需要尽可能为业务方提供易用、高效的接口,而不是为业务方提供高性能的接口。苹果平台的性能非常好。一般情况下,很少有性能不足导致的用户体验问题。Apple平台的优化手段比较有限,有时即使使出各种手段甚至不择手段牺牲稳定性,性能提升很可能也只是100ms到90ms的差距。10%的性能提升对于服务器来说是非常好的,因为服务器随时接收几十万或者几百万的访问,几十万或者几百万的10ms是非常可观的。但是对于client用户来说,10ms的差别他是感知不到的。如果用户从10s优化到9s,他还是会有一定的感知,但是如果100ms变成90ms,我觉得还是不要理会比较好。但!不重要不代表不需要做。关于性能优化,我会放在每个系列的文章中。比如网络层的优化,会写在网络层解决方案的文章中。对应每一层架构,每一层架构都有不同的优化方案,我会在各自的文章中详细介绍。
