回退?降级?熔断? CrashFallbackCore模块

当前苹果已禁止热更新补丁,如果线上出现紧急,致命的错误怎么办?立即修复发版?即使立即进行修复再加上审核最快也需要1,2天时间,在这1,2时间里可能会产生很大损失和影响,同样在这段时间内我们只能看着错误的发生等待着新版发布修复

场景

我们在实现一个页面或业务功能时候通常会依赖一些其它场景,内部逻辑方法等等, 如图
1
用户触发进入某个基金持有详情页, 在基金持有详情页会包含一些数据展现和业务功能如请求本页面数据, 进入基金详情介绍场景, 进入买入场景等,如果其中任何一个功能或服务出现错误问题会造成用户触发后出现错误,甚至导致整个APP闪退等问题,如图
2

CrashFallbackCore

CrashFallbackCore通过以方法,函数为最小颗粒度按下发的规则进行拦截隔离处理,降级处理等操作
如上面的场景,基金详情页出现错而用户正好点击触发进入基金详情页,CrashFallbackCore会根据定义的规则进行拦截并自动进入降级处理操作,如下图
3
降级处理可采用多种形式如弹出信息框告知用户, 切换到指定预设场景, 跳转到默认降级H5页面等

通过使用CrashFallbackCore我们可方便有效的防止错误的发生,同时使系统具有自动降级处理效果

实现机制

在Objective C的方法调用过程中当无法响应一个selector时,在抛出异常之前会先进入系统消息转发机制(官方文档:Message Forwarding), 消息转发过程会经历Method resolution,Fast forwarding,Normal forwarding,CrashFallbackCore内部核心机制是对消息转发最后一步forwardInvocation进行HOOK

CrashFallbackCore首先加载规则文件,根据配置的规则文件生成规则列表,再根据规则列表对已配置的类和方法进行forwardInvocation替换(替换为内部CFForwardInvocation方法),系统运行过程中当触发到对应的配置时将会进入内部CFForwardInvocation方法,在CFForwardInvocation方法中根据对应的配置规矩进行处理

效果

-w368

-w368

天弘基金移动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层对外暴露接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@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组件初期版本已经开源,可参考源码:
https://github.com/debugcheck/DynamicPatchManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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等算法实现快速读写,预加载等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@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架构的技术优化没有尽头,我们会继续以开发效率、性能、质量、新技术几个纬度不断推进,希望未来可以有更多内容分享给业内同行。

iOS HTTPS相关整理

HTTPS

HTTPS与HTTP没有任何的区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。HTTPS连接建立过程大致是,客户端和服务端建立一个连接,服务端返回一个证书,客户端里存有各个受信任的证书机构根证书,用这些根证书对服务端返回的证书进行验证,经验证如果证书是可信任的,就生成一个pre-master secret,用这个证书的公钥加密后发送给服务端,服务端用私钥解密后得到pre-master secret,再根据某种算法生成master secret,客户端也同样根据这种算法从pre-master secret生成master secret,随后双方的通信都用这个master secret对传输数据进行加密解密。

参考阅读
HTTPS那些事
SSL/TLS协议运行机制的概述
图解SSL/TLS协议

SSL证书,有以下几种方式:

  1. 使用正规的商用授权证书,需要花钱的,据说也不算贵。四五十美金一年(自己没用过)。如果有这样的证书,AFNetworking不用任何额外的设置,只是在POST或者GET是使用的URL变成https开头的就可以了。
  2. 使用自己生成SSL证书:也就是使用无效证书。这种方式也比较普遍,尤其对中小企业和个人开发来讲,免费总是好的,而且灵活自如。随时可以更换证书。对于这种方式,AFNetworking也是支持的,但需要额外的几行配置代码才行。同时,这种方式有两种不同的连接方式:
    2.1:证书不保存在手机客户端,客户端接收所有无效证书。
    2.2:客户端只接受来自特定服务器端的证书。

    Read More

谈谈消息传递机制

在当前的IOS开发中有着各种消息传递机制,包括

  1. KVO
  2. Notification
  3. Delegation
  4. Block
  5. target-action
  6. 已及当前最新的RAC(ReactiveCocoa)方式

那么我们该如何选择哪种情况下使用哪种机制呢,当然,有些情况下该使用什么机制没有唯一的答案,下面只谈谈自己对这些机制的理解

KVO

KVO是提供对象属性被改变时的通知的机制。KVO 的实现在 Foundation 中,很多基于 Foundation 的框架都依赖它,如果只对某个对象的值的改变感兴趣的话,就可以使用 KVO 消息传递,当然必须通过SET方法这种设置属性的方法才能响应KVO

Read More

个人总结的一些 APP 的代码

建立一个辅助的APP类,减少对AppDelegate的修改

最开始接触iOS开发的时候,如果需要一些全局变量或者全局函数的时候,总是直接在AppDelegate中添加,因为AppDelegate可以直接获取

1
[UIApplication sharedApplication].delegate

但是时间长了还是觉得这样不太好,AppDelegate本身有其自己的作用(对于App本身的一些事件进行处理,如启动,切换,推送),这样做感觉怪怪的,所以还是自己弄一个专门处理我们所需的全局变亮或者全局函数的对象会更好一些

Read More

读 Facebook App 头文件的一些收获(转)

引用自

最近在看一些 App 架构相关的文章,也看了 Facebook 分享的两个不同时期的架构(2013 和 2014),于是就想一窥 Facebook App 的头文件,看看会不会有更多的收获,确实有,还不少。由于在选择 ipa 上的失误,下了个 7.0 版的 Facebook(最新的是 18.1),会稍有过时,不过后来又下了个 18.1 的看了下,发现变动其实不大。以下是我从头文件中获取到的一些信息(20多万行,浏览起来还是挺累的)

让视图组件可以方便地配置

这个在 Facebook 的演讲中也提到过,自定义的 UI 组件在初始化时可以传一些数值来表示想要呈现的效果,就像 HTML 和 CSS 一样,Dom 结构表示这是什么,CSS 对该结构进行个性化定制。 Facebook 是通过 Struct 来做这件事的,比如

1
2
3
4
5
6
7
8
9
10
11
struct FBActionSheetButtonMetrics {
CDUnknownFunctionPointerType *_vptr$FBMetrics;
_Bool _initialized;
float leftMargin;
float textLeftMargin;
float bottomSeperatorSideMargin;
float bottomSeperatorHeight;
int detailMaxNumLines;
UIColor *titleColor;
//...
};

Read More

2009-360网络攻防比赛整理

360网络攻防007比赛第1题

磁盘保护挑战题
这个题一共写了5种写法的可能有重复的写法,先说说自己简单理解MBRPROT是怎么保护的首先加载MBRPROT后他会找到磁盘真实设备对象+0x28 DeviceExtension应该是个_FDO_EXTENSION结构会改变这个结构的+0xC DeviceObject 为自己的设备对象这样系统在进行读写操作时就会一步步的进入IoStartPacket函数中调用DeviceObject->DriverObject->DriverStartIo( DeviceObject, Irp )调用了MBRPROT的DriverStartIo函数然后他在自己的DriverStartIo进行判断是否为写1扇区的进行拦截。
MBRPROT拦截的流程

我的第1中写法是HOOK了IoStartPacket( 因为在IoStartPacket中会调用DriverStartIo进行读写)把调用DriverStartIo都改成atapi的DriverStartIo这样就再进行读写时就不会进到MBROPR的DriverStartIo中了。

第2中写法是自己找到真实设备对象然后把他的DeviceExtension+0xC DeviceObject改为正确的设备对象这样也不会进到MBROPR的DriverStartIo中了。
1,2种方法可以使任何软件工具都可以正常写入1扇区的。

第3种写法是自己直接发SRB写操作给atapi的DriverStartIo这样也会绕过MBRPROT的保护这样就可以写1扇区了但问题也出现了:如果说MBRPROT有2重保护的话我理解的是第1层是保护用WINHEX等不能往1扇区写入东西,就是拦截1扇区的写入,第2层是在拦截1扇区的写入同时自己也会发IRP来使1扇区清零的,这样如果你绕过第1层保护成功的写入了1扇区那你再用WINHEX查看并试着用WINHEX随便往1扇区写点东西来试MBRPROT的保护是否开启的时候MBRPROT也会让你第1次成功的写入变为没有的,这样我们刚才给atapi的DriverStartIo发的写操作也会变为没有的我采用的方法是在给atapi的DriverStartIo发写操作后HOOK atapi的DriverStartIo 判断如果是往1扇区写入操作的话我把他的Srb->DataBuffer内容改成自己的东西 因为MBRPROT清0操作会经过这里的。

Read More