随着移动互联网时代的到来,移动技术也随之飞速发展。如今,APP 已成为绝大多数互联网企业用来获取用户的核心渠道。与此同时,伴随着业务量的增长,愈来愈多的 APP 也在不断地挑战着每一个移动端研发人员的知识深度,而移动端技术人员也在这个不断接受挑战的过程中,成就了今天的移动互联网时代。
天弘基金作为一家在基金,金融行业高速发展的公司,APP 面临着多重挑战,如庞大的用户群体、高频的基金业务、交易安全可靠性等等。天弘基金移动端的开发小伙伴在技术和业务的多重压力下,不断推进着天弘 · 爱理财移动端的架构演进。
首先介绍下大环境背景,天弘 · 爱理财在前端后端使用的阿里蚂蚁金融云 MPaaS(移动即服务)平台。简单说就是通过这个平台把支付宝 App 多年的开发经验沉淀下来,帮助生态伙伴进行金融客户端的开发,提高其适应移动互联网生态的产品研发能力,同时也嵌入了移动端的安全、风控能力,并结合支付宝 APP 的众多应用场景来进行金融业务创新。
在 2015 年爱理财 App iOS 的第一个版本诞生,那时候架构很简单,基本上就是在传统的 MVC 的架构基础上封装了一个网络服务层构建而成的,当时 iOS 端整体架构如图:
爱理财 App 经历从无到有的阶段,为了快速上线抢占市场,其移动端 App 开发的 MVC 架构成了 "短平快" 思路的首选。
在早期 MVC 的体系架构中 - mPaas 层主要负责提供一些最低层的功能支持,如数据库,RPC 网络请求,分享等等 - THApiClient 层为整个 APP 网络请求的封装层,提供所有网络请求接口的请求和接受等功能 - Services 层为整个 APP 业务逻辑封装层,比如 - 实现账号登陆注册业务的 SAAccountService - 实现爱基金相关业务的 SALoveFundService - 实现银行卡相关业务的 SABankCardService - 实现买入卖出相关业务的 SABusinessService - Controller 层为 View 和 Services 层之间的一层,起到承上启下作用,提供各个模块的 UI 和业务实现的连接功能 - View 层为用户展现 UI 和用户交互 UI 层
这种架构随着版本迭代开发出现了越来越多的问题,在开发的后期会由于其超高耦和性,从而造就庞大 Controller 层,而这也是一直被人所诟病。最终的 MVC 都从 Model-View-Controller 走向了 Massive-View-Controller 的终点,其最严重的结果就是 Control 层的代码越来越多越来越臃肿难于扩展维护,同时 Control 层和 View 层之间存在一些较高的耦合。
基于上述我们遇到的问题,我们在原来的传统架构上又做了重新调整和优化,提出了 iOS 端架构 V2.0,
在爱理财 V2.4.0 版本项目内开始逐步重构采用 MVVM + 分层架构模式解耦,使越来越臃肿的 Controller 层逐步缩小并分解解耦,业务逻辑分模块下沉。调整后的架构如下:
在原有的 Controller 层和 Service 层之间插入了一个 ViewModel 层 (紫色的), 对于此次架构调整优点如下:
调整前 | 调整后 |
---|---|
Controller 层过于复杂 | Controller 层只用来做中转层不参与业务逻辑等处理 |
老的 Controller 层包含了业务逻辑代码使此层的代码量超大并且臃肿不易维护 | Controller 层对上 (View 层) 只提供页面展示所需数据, 对下调用 (ViewModel 层) 暴露出的业务逻辑接口 |
Controller 层包含业务逻辑不能较好, 灵活的扩充, 分隔等 | ViewModel 层实现整个业务逻辑, 实现对上层只提供接口因此此层灵活, 易维护 |
不能进行功能, 业务逻辑的单元测试 | 方便进行功能, 业务逻辑的单元测试 |
我们在 2.0 版本架构中完成了内部竖向解耦,在 V3.0 版本(当前正在内部测试阶段)架构中我们将逐步实现各个层同层内部中子模块的解耦工作(横向解耦)如同层之间各个子模块之间调用相互依赖, 严重影响各个模块之间的解耦, 如 A 模块内部 (甚至外部) 依赖 B,C 模块而 B,C 模块又依赖 A 模块, 这种相互依赖相互 include 的情况导致各个模块相互不能独立, 严重影响编译速度和扩展性,灵活性等, 当前在 V3.0 版本中为了完成横向解耦我们内部开发实现一个动态路由组件(DR)如下图:
关于动态路由组件(DR),是一套可根据规则或下发规则自动实现页面跳转流转的组件,其主要目的为了模块间可以方便容易的横向解耦,拆分,路由,降级容错等初衷。
THApiClient 层作为网络请求层在整个架构中不可或缺同时也为整个 APP 网络请求的封装层,提供所有网络请求接口的请求和接受,数据对象处理转换等功能, 此层如下图:
此 THApiClient 层对外暴露接口如下:
- @interfaceTHApiClient : NSObject
- + (instancetype)defaultClient;/**
- * RPC 网络请求
- *
- * @paramoperationType Operation type
- * @paramserviceType Service type
- * @paramparameters 请求参数
- * @paramresultType 期望返回的对象类型
- * @paramblockType Callback type
- * @paramblock Callback
- */- (void)requestWithOperationType:(NSString *)operationType
- serviceType:(NSString *)serviceType
- parameters:(NSArray *)parameters
- resultType:(NSString *)resultType
- BlockType:(THBlockType)blockType completeBlock:(VoidBlock)block;/**
- * AFNetWork 网络请求
- *
- * @paramhttpRequest request type
- * @paramresultClass 期望返回的对象类型
- * @paramblockType Callback type
- * @paramblock Callback
- */- (void)fetchDataWithRequest:(THNetWorkHttpRequest * _Nonnull)httpRequest resultClass:(Class)resultClass BlockType:(THBlockType)blockType completeBlock:(VoidBlock)block;
- ...
在爱理财 App 2.1 版本时候我们加入了此组件,在 iOS App 端我们使用的是基于 JSPatch 框架并根据我们自己的业务需求等情况在 JSPatch 基础上封装实现的,加入此组件初衷是在发现问题时第一时间修复线上出现的紧急问题,紧急 Crash 等 Bug 功能,避免给用户带来影响。
DynamicPatchKit 组件初期版本已经开源,可参考源码: https://github.com/debugcheck/DynamicPatchManager
- typedefvoid(^DynamicPatchReturnBlock)(NSError *error, NSDictionary *dynamicPatchConfiguration);
- typedef NS_ENUM(NSUInteger, DynamicPatchErrorCode) {
- DynamicPatch_ScriptCrash =10000,
- DynamicPatch_ScriptNonExistent,
- DynamicPatch_ScriptVerifyError,
- DynamicPatch_ScriptRepeatRequest,
- };@interfaceDynamicPatchManager : NSObject
- + (instancetype)sharedManager;/**
- * 返回当前脚本
- *
- * @return<#return value description#>
- */- (NSString *)decryptPatchScript;/**
- * 执行本地MainBundle中的脚本
- *
- * @paramname <#name description#>
- * @paramtype <#type description#>
- */- (void)evaluateScriptInMainBundleForName:(NSString *)name type:(NSString *)type;/**
- * 执行已经请求的最新脚本,Block返回脚本信息
- *
- * @paramcompleteBlock <#completeBlock description#>
- */- (void)excutePatchScript:(DynamicPatchReturnBlock)completeBlock;/**
- * 请求最新脚本,Block返回脚本信息
- *
- * @paramcompleteBlock <#completeBlock description#>
- */- (void)requestPatchScriptWithCompleteBlock:(DynamicPatchReturnBlock)completeBlock;/**
- * 请求最新脚本并执行, Block返回脚本信息 (这个只是合并上面2个接口)
- *
- * @paramcompleteBlock <#completeBlock description#>
- */- (void)requestPatchScriptAndRunWithCompleteBlock:(DynamicPatchReturnBlock)completeBlock;@end
在整个项目中各个页面,各个业务逻辑等等的一些功能点上都需要一些存储(数据库)相关的功能,比如数据,网络请求的缓存,为了提供更好的用户体验在内部某些数据从服务器拿到后需要先缓存起来保证在无网或其他一些情况下也能正常显示,预加载出来等等。
此组件主要功能是实现把内部用到的所有存储功能进行统一,避免分散,对外暴露简单方便的接口调用同时对上层使用者来说存储功能接口透明,不用关心底层存储的具体实现,而在底层可自由方便切换存储源,也可实现基于用户相关,用户无关的存储,如在存储数据时可根据用户来区分所存储的数据是否与用户有关,是否与用户无关的,并且此组件在内部已根据 LRU 等算法实现快速读写,预加载等功能。
- @interfaceTHViewControllerCacheManager : NSObject
- + (instancetype)sharedInstance;/**
- * 注册一个CacheProvide
- *
- * @paramcacheProvide cacheProvide 实现类
- * @paramname <#name description#>
- *
- * @return<#return value description#>
- */- (BOOL)registerCacheProvideWithClass:(Class)cacheProvide forName:(NSString *)name;/**
- * 通过数组注册CacheProvide
- *
- * @paramcacheProvideArray <#cacheProvideArray description#>
- */- (void)registerCacheProvideWithArray:(NSArray *)cacheProvideArray;/**
- * 卸载已注册的CacheProvide类
- *
- * @paramname <#name description#>
- */- (void)unregisterCacheProvideForName:(NSString *)name;/**
- * 查找某一个CacheProvide
- *
- * @paramname <#name description#>
- *
- * @return<#return value description#>
- */- (id)findCacheProvideByName:(NSString *)name;/**
- * 预取所有CacheProvide类中的存储对象
- */- (void)fetchAllCacheProvide;/**
- * 刷新所有CacheProvide类中的存储对象
- */- (void)refreshAllCacheProvide;/**
- * 刷新用户相关的CacheProvide类中的存储对象
- */- (void)refreshAllUserCacheProvide;@end
此组件更多信息请参见稍后开源代码。
目前在爱理财 iOS App 中我们已经使用到了 RN 技术,并且在 iOS 端的一部分页面也使用了 RN 页面,当初在我们内部建设 RN 主要目的是为了加快响应业务需求和加快开发速度,提升客户端对业务变化的响应能力,在 iOS 客户端内部所使用的 RN 架构主要使用的是单(全局)Bridge 模式介绍如下:
单(全局)Bridge 模式优点: 1. 管理简单 2. 性能优越 3. 满足多 RNView 的需求
但也存在一定的缺点: 1. 缺少隔离性 2. 内核有问题会导致整体 Crash
目前我们已经对缺点 2 做了一些优化和处理如: 当 RN 内核或 Bridge 出现问题时会进行降级处理如 RN 页面加载失败或出现错误时候会自动降级到 H5 页面或指定特殊页面等,我们为了快速方便的在 App 内部使用 RN 对于一些常用的组件我们开发完善了一套 RN 组件库如:RNView,RNCell,RNPlugin,RN 网络通信库等,同时对 RN 的打包发布目前我们也写了一套自动打包发布脚本,当前我们使用运用 RN 技术还处在初期发展阶段,在未来我们会从对 RN 的监控,运维,统计等多方向继续完善优化属于我们自己的 THRN 库和一些配套环境。
该组件独立于 App 内任何业务逻辑,UI 逻辑,统一实现做到一个组件库就可实现基本常用埋点需求,并且埋点位置,埋点逻辑,埋点事件等可以动态下发给 APP,可以实现动态的根据需求添加,删除埋点功能,最大实现自动、动态可配、正确的收集用户在使用 App 时的所需事件数据,该组件的整体框架采用了 AOP(Aspect-Oriented-Programming)即面向切面编程的思想,就是动态的在函数调用前后插入数据收集的代码,其中数据收集点主要根据内嵌或下发的配置文件进行打点收集,如下示例配置文件: 组件中还使用了一些其他技术如页面路径的优化,对收集点所需参数的取值等,此处仅做下简单介绍。
在天弘 · 爱理财 App 内部中还有一些其他优秀的组件如启动保护组件,Crash 安全保护组件,H5 容器组件等等,我们相信一个好的优秀的 App 离不开内部优秀的架构框架同样也离不开一些基础组件的建设,就如同修建盖一座楼一样优秀好的框架架构是楼基础,完善易用的组件是楼一砖一瓦,只有基础有了砖瓦完善了才有机会把楼修得高修的雄伟起来。
总之,App 架构的技术优化没有尽头,我们会继续以开发效率、性能、质量、新技术几个纬度不断推进,希望未来可以有更多内容分享给业内同行。
姚宇,天弘基金移动平台 iOS 组负责人、移动平台核心架构组成员,负责天弘基金移动 iOS 平台客户端框架的技术规划和相关的开发工作
来源: http://blog.csdn.net/yaoyu/article/details/70184539