iOS组件化辨析

iOS开发相对于其他开发语言来说是个很年轻的开发方式,很多开发理念是与其他语言不谋而合的。随着客户端软件规模和复杂性的不断增加,在软件设计中,软件的局部和整体的系统结构显得越来越重要,对此人们提出了软件体系结构的概念,一种方式即我们所熟知的组件化开发方案。

针对于组件化的技术方案,Limboy分享了两篇关于蘑菇街的组件化的实现,分别是《蘑菇街 App 的组件化之路》《蘑菇街 App 的组件化之路·续》。基于Limboy的分享,Casa提出了一些意见,并在博客上分享出来,即《iOS应用架构谈 组件化方案》。最后,Bang在二人的基础上结合自己的思考对三种方案做了一番梳理,即《iOS 组件化方案探索》。通过这些文章,对于移动应用开发中的组件化方案基本上就有了一个大体的理念了。本文将在前人的基础上系统的梳理一遍,以满足日常开发的需要。

本文包括:

  • 组件化的历史
  • 组件化的优势
  • 组件化的系统实现

组件化的历史

这一段完全可以跳过去不读。不过读读也无妨。 -- 刘心武《钟鼓楼》

自从1948年6月21日,软件模式在曼切斯特大学诞生以来,软件体系结构经历了四个阶段:无结构、萌芽、初级和高级阶段。自上世纪 90 年代步入高级阶段以来,软件开发的目标是使软件具备较好的自适应性、互操作性、可扩展性和可重用性,软件开发强调采用构件化技术和体系结构技术。

在项目的实施阶段,体系结构是建立开发人员的组织、分工、协调开发人员关系和配合的依据。在项目的维护升级阶段,对软件的任何扩充和修改都要在体系结构的指导下进行,以维护整体设计的合理性和正确性,并为维护升级的复杂性和代价分析提供依据。

在日常开发中,程序员一直考虑的一个问题是如何让代码变的简单。抛开技术大牛和大神程序员这条路(毕竟开发人员是一种金字塔结构),最后自然而然形成的一套思路就是大团队的协同合作。经过长时间的积累,软件开发人员借鉴了硬件组成原理(如CPU从提升主频到多核的改变),基于组件式程序设计思想,提出了组件式软件体系结构,这一理论给软件开发工程注入了无限的活力。进而牵涉到的两个原则就是:内聚性和耦合性,也就是架构设计中所谓的高内聚、低耦合。通俗来讲就是:一个模块实现所需要的全部功能(内聚性),而不需要其他人辅助实现;并且代码不会影响到别的模块(低耦合性)。

组件化一定程度上可以约等于模块化,调用者只需关注输入和输出,相互之间没有影响,组件化本质的一点就是封装。每个组件对应一个工程目录,组件所需的各种资源都在这个目录下就近维护;每个组件相对独立,界面只不过是组件的容器,组件自由组合形成功能完整的界面;当不需要某个组件,或者想要替换组件时,可以整个目录删除/替换。组件化只是所有GUI开发的一种思路,总思想就是分而治之、重复利用。

组件化通常有以下几个要素:

1)组件是对逻辑的封装,不限于UI元素。

2)组件具备单个可移植性,即“随加载随用”,不需要为其准备复杂的基础条件。

组件是一个广义上的概念,并不一定都是页面跳转,还可以是其他不具备UI属性的服务提供者,比如日志服务,VOIP服务,内存管理服务和其他帮助服务等,即在更高的维度去封装功能单元。组件化开发一个最重要的特点就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式。对这些功能单元进行进一步的分类,才能在具体的业务场景下做更合理的设计。这些在MrPeakTeach的《iOS组件化方案》中也做了相应的讨论。

组件可以分为以下几类:

1)带UI属性的独立业务模块。

这些组件有很具体的业务场景,如App的主页模块、登录注册模块、分享模块等。这类模块一般有个入口Controller,可以通过Push或Present的方式作为入口接入。Controller作为页面的基本单位和Web Page有很高的相似度,蘑菇街采取URL注册的实现方式或是天猫的统跳协议等,采用类URL的方式标记本地的每一个Controller,不仅方便本地的跳转,还能支持Server下发跳转指令,对于业务的兼容性有很大的帮助。

从理论上来说,组件化和URL本身并没有什么联系,URL只是接入组件的方式之一。

2)不具备UI属性的独立业务模块。

这类模块不具备UI场景,但却和具体的业务相关,如日志上报模块、埋点统计等。组件被调用分为远程和本地,这种日志服务的调用是本地类型的调用,用URL来标这类记本地服务多有不便。

3)不具备业务场景的功能模块。

这类模块和具体的业务场景无关,如数据模块(提供数据的读写服务,包含多线程的处理等)、Network模块和图片处理类等。这些模块可以被任意模块使用,但不和任何业务相关。这种组件属于我们app的基础服务提供者,更像是一个个SDK,或是帮助类。通过Pods使用的很多著名第三方库都属于这一类,像FMDB,SDWebImage等。

组件其实不是那么好进行抽象设计的,组件可以横向组件,但也要考虑纵向复用的问题,不得不牵涉的问题就是耦合问题。还有的就是组件的粒度问题,组件要细分到何种程度,都是在架构设计中需要考虑的,这些问题我们在具体使用时都需要考虑。

组件化的优势

组件化编程的关键目的是为了将程序模块化,使各个模块之间可以单独开发,单独测试。组件式体系结构把程序的功能分散在各个不同的组件中来完成,组件是可独立开发的程序模块,它能够动态地插入到系统中,并且可以被自由地删除和替换。

组件化能够提高软件开发的并行性和开发效率,降低设计开发难度,缩短开发周期,增强应用程序的可运行性、可测试性和可维护性。归纳起来就是:

  • 组件化能够提高软件的复用度。

  • 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,以满足用户不断变化的需求,缩短项目交付周期。

  • 组件化因为强大的独立性,可以提高软件开发的并行性,为软件产业的大规模生产提供支持,便于协同开发。

  • 提高可维护性,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单。

由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级。例如某个组件负责处理异步请求,与业务无关,我们添加缓存机制,序列化兼容,编码修正等功能,一来整个系统中的每个使用到这个组件的模块都会受惠;二来可以使这个组件更具健壮性。由于代码中的耦合度降低了,每个模块都可以分拆为一个组件,团队中每个人发挥所长维护各自组件,对整个应用来说是精细的打磨。

组件化的系统实现

如果您接触过早期的项目,OC中的代码都是写在一起的,里面会分成各种Tools、Helper等,现在看起来很不优雅。不过,这也是程序的发展过程,我想如果您见多识广,即使是现在仍会碰到一些认为移动端就是显示显示UI的领导,大千世界各有不同罢了。

我始终认为,即使是一个普通的开发人员也是需要一些架构观的,从项目的整体考虑对一个技术人的成长是有很大帮助的。本次关于架构方面的内容不会涉及很多,重点还是在架构中的组件化这个方面。

在我们早期的组件化实现中,会实现一个Manager类,里面封装着所有的跳转逻辑,外界调用组件都通过这个类实现,因此Manager对其他类也不得不产生耦合,因为需要#import相关的类。即Bang所说的中间件耦合。

图1

当组件里的类被移除或是实现类改变了都需要在Manager类里做出改变。

和Bang的思路相同,通过Runtime反射调用可以解除耦合。

+ (UIViewController *)TestComponent_viewController:(NSString *)some {
 Class cls = NSClassFromString(@"TestComponent");
 return [cls performSelector:NSSelectorFromString(@"detailViewController:") withObject:@{@"some":some}];
}

如此便可用调用组件里的TestComponent类里的detailViewController方法

+ (UIViewController *)detailViewController:(NSString *) some {
 DetailViewController *detailVC = [[DetailViewController alloc] initWithSome: some];
 return detailVC;
}
图2

由此,便解除了import头文件的耦合问题。import头文件算一种耦合,因为头文件缺失会导致编译出错。业务耦合是另一种维度的耦合,这种方式对业务耦合并没有多大的改观。如果组件方修改了业务接口,即使你能编译通过,你所调用的组件也无法正常工作了。此时便需要做一些容错处理了,当业务无法实现时,相应的做一些处理,这些需要针对不同的业务流进行操作。如Hybrid跳转,如果不能跳转就跳到web,因为web加载只需要一个URL。但是本地的实现逻辑呢,这是一个问题。

针对这个问题Mrpeak给出了一些建议,在组件化上更倾向于采用Distributed Design方式(如图3)。各个组件”自扫门前雪“,用规范的protocol声明,加上严格的版本控制来提供组件服务,称之为Protocol+Version方案。

图3

因为采用Centralized Design(图1),Centralized设计在Node增加的情形下会增加中央节点的负担。Mrpeak以IP协议的路由寻址算法(Distributed Design Vs Centralized Design的一个经典场景)进行了举例分析。这种方法会有耦合,在业务改动后编译时会报错。但是,回到我们上面使用的Manager类,同样可以实现。针对Mrpeak的分析,反革命攻城狮CasaTaloyum对其做了一些回应,具体可以参考《关于MrPeak的组件化文章的回应》

基于上面的分析,我们在具体的项目中也实现了Protocol的相关逻辑,但更多使用的是在一个模块内的逻辑,如首页复杂的实现逻辑等,采用工厂模式+代理模式实现。

结合Casa和Limboy的解决方案,Bang的实现方式对于不具备业务场景的功能模块的实现有很好的通用性。但是,如果要实现统跳协议,这种实现逻辑就有些捉襟见肘了。统跳协议的内容可以参考天猫的《解耦神器 —— 统跳协议和Rewrite引擎》。我们的目的是合理的实现功能,所以在具体的使用中并不局限于一种方式。相反的,结合不同处理逻辑的优缺点,在项目的构建时都做了对应的处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容