天弘基金移动 App 客户端架构优化之路

3,711 阅读11分钟

天弘基金移动App客户端架构优化之路

随着移动互联网时代的到来,移动技术也随之飞速发展。如今,APP已成为绝大多数互联网企业用来获取用户的核心渠道。与此同时,伴随着业务量的增长,愈来愈多的APP也在不断地挑战着每一个移动端研发人员的知识深度,而移动端技术人员也在这个不断接受挑战的过程中,成就了今天的移动互联网时代。

天弘基金作为一家在基金,金融行业高速发展的公司,APP面临着多重挑战,如庞大的用户群体、高频的基金业务、交易安全可靠性等等。天弘基金移动端的开发小伙伴在技术和业务的多重压力下,不断推进着天弘·爱理财移动端的架构演进。

首先介绍下大环境背景,天弘·爱理财在前端后端使用的阿里蚂蚁金融云MPaaS(移动即服务)平台。简单说就是通过这个平台把支付宝App多年的开发经验沉淀下来,帮助生态伙伴进行金融客户端的开发,提高其适应移动互联网生态的产品研发能力,同时也嵌入了移动端的安全、风控能力,并结合支付宝APP的众多应用场景来进行金融业务创新。

天弘·爱理财iOS版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层之间存在一些较高的耦合。

App 2.0版本架构

基于上述我们遇到的问题,我们在原来的传统架构上又做了重新调整和优化,提出了iOS端架构V2.0,

在爱理财V2.4.0版本项目内开始逐步重构采用MVVM+分层架构模式解耦,使越来越臃肿的Controller层逐步缩小并分解解耦,业务逻辑分模块下沉。调整后的架构如下:

image

在原有的Controller层和Service层之间插入了一个ViewModel层(紫色的), 对于此次架构调整优点如下:

  1. Controller层只用来做中转层不参与业务逻辑等处理
  2. Controller层对上(View层)只提供页面展示所需数据,对下调用(ViewModel层)暴露出的业务逻辑接口
  3. 方便进行功能,业务逻辑的单元测试
  4. ViewModel层实现整个业务逻辑,实现对上层只提供接口因此此层灵活,易维护
调整前 调整后
Controller层过于复杂 Controller层只用来做中转层不参与业务逻辑等处理
老的Controller层包含了业务逻辑代码使此层的代码量超大并且臃肿不易维护 Controller层对上(View层)只提供页面展示所需数据,对下调用(ViewModel层)暴露出的业务逻辑接口
Controller层包含业务逻辑不能较好,灵活的扩充,分隔等 ViewModel层实现整个业务逻辑,实现对上层只提供接口因此此层灵活,易维护
不能进行功能,业务逻辑的单元测试 方便进行功能,业务逻辑的单元测试

App 3.0版本架构

我们在2.0版本架构中完成了内部竖向解耦,在V3.0版本(当前正在内部测试阶段)架构中我们将逐步实现各个层同层内部中子模块的解耦工作(横向解耦)如同层之间各个子模块之间调用相互依赖,严重影响各个模块之间的解耦,如A模块内部(甚至外部)依赖B,C模块而B,C模块又依赖A模块,这种相互依赖相互include的情况导致各个模块相互不能独立,严重影响编译速度和扩展性,灵活性等, 当前在V3.0版本中为了完成横向解耦我们内部开发实现一个动态路由组件(DR)如下图:

image

关于动态路由组件(DR),是一套可根据规则或下发规则自动实现页面跳转流转的组件,其主要目的为了模块间可以方便容易的横向解耦,拆分,路由,降级容错等初衷。

THApiClient层介绍

THApiClient层作为网络请求层在整个架构中不可或缺同时也为整个APP网络请求的封装层,提供所有网络请求接口的请求和接受,数据对象处理转换等功能,此层如下图:

image

此THApiClient层对外暴露接口如下:

@interface THApiClient : NSObject

+ (instancetype)defaultClient;

/**
 *  RPC 网络请求
 *
 *  @param operationType Operation type
 *  @param serviceType   Service type
 *  @param parameters    请求参数
 *  @param resultType    期望返回的对象类型
 *  @param blockType     Callback type
 *  @param block         Callback
 */
- (void)requestWithOperationType:(NSString *)operationType
                     serviceType:(NSString *)serviceType
                      parameters:(NSArray *)parameters
                      resultType:(NSString *)resultType
                       BlockType:(THBlockType)blockType completeBlock:(VoidBlock)block;


/**
 *  AFNetWork 网络请求
 *
 *  @param httpRequest     request type
 *  @param resultClass     期望返回的对象类型
 *  @param blockType       Callback type
 *  @param block           Callback
 */
- (void)fetchDataWithRequest:(THNetWorkHttpRequest * _Nonnull)httpRequest resultClass:(Class)resultClass BlockType:(THBlockType)blockType completeBlock:(VoidBlock)block;

...

动态补丁组件(DynamicPatchKit)

在爱理财App 2.1版本时候我们加入了此组件,在iOS App端我们使用的是基于JSPatch框架并根据我们自己的业务需求等情况在JSPatch基础上封装实现的,加入此组件初衷是在发现问题时第一时间修复线上出现的紧急问题,紧急Crash等Bug功能,避免给用户带来影响。

DynamicPatchKit组件初期版本已经开源,可参考源码:
github.com/debugcheck/…

typedef void (^DynamicPatchReturnBlock)(NSError *error, NSDictionary *dynamicPatchConfiguration);

typedef NS_ENUM(NSUInteger, DynamicPatchErrorCode) {

    DynamicPatch_ScriptCrash = 10000,
    DynamicPatch_ScriptNonExistent,
    DynamicPatch_ScriptVerifyError,
    DynamicPatch_ScriptRepeatRequest,
};


@interface DynamicPatchManager : NSObject

+ (instancetype)sharedManager;

/**
 *  返回当前脚本
 *
 *  @return <#return value description#>
 */
- (NSString *)decryptPatchScript;


/**
 *  执行本地MainBundle中的脚本
 *
 *  @param name <#name description#>
 *  @param type <#type description#>
 */
- (void)evaluateScriptInMainBundleForName:(NSString *)name type:(NSString *)type;


/**
 *  执行已经请求的最新脚本,Block返回脚本信息
 *
 *  @param completeBlock <#completeBlock description#>
 */
- (void)excutePatchScript:(DynamicPatchReturnBlock)completeBlock;


/**
 *  请求最新脚本,Block返回脚本信息
 *
 *  @param completeBlock <#completeBlock description#>
 */
- (void)requestPatchScriptWithCompleteBlock:(DynamicPatchReturnBlock)completeBlock;


/**
 *  请求最新脚本并执行, Block返回脚本信息 (这个只是合并上面2个接口)
 *
 *  @param completeBlock <#completeBlock description#>
 */
- (void)requestPatchScriptAndRunWithCompleteBlock:(DynamicPatchReturnBlock)completeBlock;

@end

内部统一存储组件

在整个项目中各个页面,各个业务逻辑等等的一些功能点上都需要一些存储(数据库)相关的功能,比如数据,网络请求的缓存,为了提供更好的用户体验在内部某些数据从服务器拿到后需要先缓存起来保证在无网或其他一些情况下也能正常显示,预加载出来等等。

此组件主要功能是实现把内部用到的所有存储功能进行统一,避免分散,对外暴露简单方便的接口调用同时对上层使用者来说存储功能接口透明,不用关心底层存储的具体实现,而在底层可自由方便切换存储源,也可实现基于用户相关,用户无关的存储,如在存储数据时可根据用户来区分所存储的数据是否与用户有关,是否与用户无关的,并且此组件在内部已根据LRU等算法实现快速读写,预加载等功能。

@interface THViewControllerCacheManager : NSObject

+ (instancetype)sharedInstance;

/**
 *  注册一个CacheProvide
 *
 *  @param cacheProvide cacheProvide 实现类
 *  @param name         <#name description#>
 *
 *  @return <#return value description#>
 */
- (BOOL)registerCacheProvideWithClass:(Class)cacheProvide forName:(NSString *)name;


/**
 *  通过数组注册CacheProvide
 *
 *  @param cacheProvideArray <#cacheProvideArray description#>
 */
- (void)registerCacheProvideWithArray:(NSArray *)cacheProvideArray;


/**
 *  卸载已注册的CacheProvide类
 *
 *  @param name <#name description#>
 */
- (void)unregisterCacheProvideForName:(NSString *)name;


/**
 *  查找某一个CacheProvide
 *
 *  @param name <#name description#>
 *
 *  @return <#return value description#>
 */
- (id)findCacheProvideByName:(NSString *)name;


/**
 *  预取所有CacheProvide类中的存储对象
 */
- (void)fetchAllCacheProvide;


/**
 *  刷新所有CacheProvide类中的存储对象
 */
- (void)refreshAllCacheProvide;


/**
 *  刷新用户相关的CacheProvide类中的存储对象
 */
- (void)refreshAllUserCacheProvide;

@end

此组件更多信息请参见稍后开源代码。

React-Native组件相关

目前在爱理财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)即面向切面编程的思想,就是动态的在函数调用前后插入数据收集的代码,其中数据收集点主要根据内嵌或下发的配置文件进行打点收集,如下示例配置文件:
image
组件中还使用了一些其他技术如页面路径的优化,对收集点所需参数的取值等,此处仅做下简单介绍。

在天弘·爱理财 App内部中还有一些其他优秀的组件如启动保护组件,Crash安全保护组件,H5容器组件等等,我们相信一个好的优秀的App离不开内部优秀的架构框架同样也离不开一些基础组件的建设,就如同修建盖一座楼一样优秀好的框架架构是楼基础,完善易用的组件是楼一砖一瓦,只有基础有了砖瓦完善了才有机会把楼修得高修的雄伟起来。

总之,App架构的技术优化没有尽头,我们会继续以开发效率、性能、质量、新技术几个纬度不断推进,希望未来可以有更多内容分享给业内同行。