在2017年在线技术峰会——阿里开源项目最佳实践上,来自天猫的戴鹏带来了BeeHive关于iOS大型项目解耦方案的分享。他从多人开发的问题介绍了业界的三种模块方案,将传统Init与BeeHive Module进行了比较,对BeeHive的架构进行了分析,并对Module Manager和Module的实现进行了详细介绍。
以下内容根据直播视频整理而成。
视频回顾:点击进入
Pdf下载:点击进入
在iOS大型项目中如何抽丝剥茧把对应的需要优化的地方进行优化、多人协作开发中如何避免冲突?本文为你详细解答。
背景
2015年上半年,开始参与天猫iPad开发,负责的主要内容包括我的天猫以及首页一些程序框架性的东西。2015年下半年之后,开始参与天猫iPhone开发,主要负责直播项目。
多人开发模式下遇到的问题
在多人开发模式中涉及到了沟通交流以及互相合作的问题。在代码依赖层面,功能调用比较繁琐,功能之间互相依赖,底层功能反向依赖上层业务。在协同开发层面,合并冲突,费时费力,合作开发、接口不稳定,频繁更新接口。在量化分析层面,代码耦合难以分析性能、难以做单元测试。在性能优化层面,每个库侵入App生命周期,难区分库初始化必要性,导致启动卡顿。
恶劣的开发环境像面条式的逻辑文化。老练的开发人员也会畏手畏脚,因为整个迭代周期流程中包含了开发过程以及修bug过程,如果在一个耦合严重的情况下做这些操作的话,可能造成更多bug的引入。整个工程如果没有做到合理的分类的话,其实业务和基础逻辑就会交织缠绕在一起,可维护性降低。
多人开发的小目标
在开发过程中,为了支持业务的快速发展,并没有认真思考整个应用该具备哪些特点。而所有的应用必须具备以下三个特点:维护性,支持多人持续开发;可用性,有良好性能及功能完备;可分析性,支持优化、用户行为分析。
业界的模块方案
URL Router可以参考开源界比较成熟的方案JR Route,把每一个模块以一个URL的形式作为独立存在的界限范围。URL Router需要在内存中维护一张URL表作为key来实现对应的push或者唤起对应的模块。它在传递参数过程中不能传递类似于对象这样的数据,只能传递String这类的数据。
Target Action也是基于苹果原生的Runtime的优势,其优势很大在于可以做到无需加载内存,并且在调用中没有相关的依赖。在调用中的缺陷在于只有在运行池中才可以知道对应的组件是否存在,若不存在会造成对应的Crash。
Protocol class是利用原始class以及对应的Protocol用到的Service interface去进行实现以及接口的分离,让大部分的业务方只需要基于Protocol的interface进行调用。其过程中会涉及到内存占用的问题。
BeeHive实践之路
BeeHive相当于一个蜂巢,我们希望每个模块都像六角形的蜂窝可以独立存在,可以同时对外有很好的接口。
一个App的层次关系
iOS分层有几种层次:核心的Core OS层、其上的Core Services和Media Layer、Cocoa Touch对于开发者人员来说是比较黑盒的东西。而一个个独立的App也类似于插件的形式插在系统当中。最上层的应用生命周期是管控平台。
传统Init与BeeHiveModule
传统的Init有三个层次,此过程中Init没法做到很好的区分,并且其中有些比较耗时的操作是没有必要的。对应的BeeHive Module是把一个个module进行了细粒度的拆分,通过Module Manager的形式将整个应用的生命周期进行了发放,每个module在自己的回调过程中只需要遵循module protocol就可以感应到整个应用的生命周期,在不同场景下做对应的程序响应。
Module Manager
Module Manager除了拆分应用的生命周期还扩充了生命周期。从上下图对比当中我们可以知道,BeeHive中加了三块流程,ModuleSetup、ModuleInit、ModuleSplash,这三个流程对于大部分模块的初始化来说已经足够用了。在这个过程中,还支持了自定义的ModuleEvent,可以在任意一个App的生命环节中做到插入式的回调,这些回调便于在某些场景中展示某项业务功能的定制化内容。
Module
Module存在的意义在于:拆解主工程中耦合;分发状态;定义App状态;量化分析模块耗时;支撑横向扩展能力。
在ModuleSetup和ModuleInit时,都对Module的耗时进行了记录。Module Monitor做到了记录耗时、check主线程占用、记录对应模块加载的顺序过程。Module Monitor也是作为插件植入到了BeeHive中。
从BeeHive里面的工程可以看出来Module交易模块、用户买点、业务Home、shop的加载情况。这些数据都可以为之后的性能优化做支撑。
BeeHive架构
在BeeHive Core里,每一个module的业务调度都是以一个插件的形式存在。Module的注册、Service的注册,BeeHive提供几种形式:写在plist里,注解,动态调用。而Context里面主要包含了一些全局性的内容,比如服务端返回的整个App环境,Config里面也存储了全局可读的状态便于整个模块共享资源。
几大业务并行调用流程
上图中三个不同的业务在调用过程中涉及到了4个基础库的模块。可以看出,其中很多的关联关系是没有必要存在的,比如日志库到网络库、网络库到日志库只需要单点的依赖就可以了。
调整后的调用流程
BeeHive对Service Module进行了一个处理,Service Manager主控了Service Protocol的实现,比如管理了单例或者多例的情况。所有的业务不再直接依赖各种库,而是通过Interface的形式,业务就以一个切片的形式只需要关心业务的流程,大大提升了工作效率。
Service阻隔模块间依赖
如上图所示,Service阻隔模块间依赖是以一个中心化的方式把原本无序化面条式的业务耦合变成了新形式的耦合。Service存在这样的特性:Service吸收了Spring Service的开发理念,以一种AOP的形式存在;Register和Module一样存在着三种模式,静态注册、代码注册、注解注册;是否单例和多例也和Spring Service保持一致。
Protocol-class对于编译性能的影响
在大工程的开发过程中,会遇到Xcode跑的很慢的情况,也许只是改变了几个小的文件,但是这些文件在编译、打包的过程中耗时还是比较严重的。一方面可以买新的电脑来提升编译速度,或者多开几个Xcode线程达到提升性能的目的。Protocol-class可以通过三方面来提升编译的速度:分配、分离接口和实现,通过Protocol分割在不同的模块来做代码间直接依赖的隔断;Service实现合并编译也可提升相应速度,因为单独一个模块当中提供的一些Service可以合并到大的整个的Module Service中,更少的文件也就提高了编译效率;业务引入更少头文件对编译速度也有一定的提升。
BeeHive环境下开发3DTouch
非BeeHive环境下开发3DTouch功能的步骤如上图所示。各种需求带来模块的引用。首先,动态化需要引入相关的配置内容,前后台切换以及个性化Quick Action涉及到本地存储,更进一步的个性化用到了用户埋点相关的东西。对外由于需要记录各种数据,在没有服务端的配合下需要对外提供一些Service。
在BeeHive环境下开发这样功能需要关注的点很少,不需要引入这样那样的模块,只需要引入一个Module关注它希望在整个生命周期中想要知道的内容。对应期待的服务只要依赖对应的Service Interface就行。对外提供的Service也是以BeeHive的Service Manager作为中转。
Module+Service架构具备的特点
每一个都是独立模块,又享有了全局的Context、Service Interface,对横向扩展具有插件式的开发。引入新的业务会非常独立,没有耦合的情况。对于移植来说,只要以一个Module形式存在并且新的工程当中也是以BeeHive作为基础的话,可以做到即拿即用。
BeeHive的展望与问题
支持App秒开的智能调度逻辑,对于Module的加载分析还是以单纯的记录,不能做到智能加载的过程。需要支持更多的Service调用形式。Service依赖以及调度逻辑也存在一些问题。
BeeHive的框架带来了一定的学习成本,只有在一定复杂的情况下,BeeHive的价值才能够体现出来。复杂度守恒原理也提到,将业务间的耦合剥离开来会造成BeeHive内部的复杂,通过了一些线程的保护避免了一些快速的情况。BeeHive希望以一个基础库的形式管理整个应用的周期以及对应的外部插件。