[转载] iOS热修复,看这里就够了

[转载] iOS热修复,看这里就够了

原文地址

背景

对于 app store的审核周期不确定性,可长到2星期,短到1天。假如线上的应用出现了一些bug,甚至是致命的崩溃,这时候假如按照苹果的套路乖乖重新发布一个版本,然后静静等待看似漫无期限的审核周期,最终结果就是:用户大量流失。因此,对于一些线上的bug,需要有及时修复的能力,这就是所谓的热修复(hotfix)。

随着迭代频繁或者次数的增多,应用出现功能异常或不可用的情况也会随之增多。这时候又有什么方法可以快速解决线上的问题呢?

  1. 第一、在一开始功能设计的时候就设计降级方案,但随之开发成本和测试成本都会双倍增加;

  2. 第二、每个功能加上开关配置,这样治标不治本,当开关关掉的时候,就意味着用户无法使用该功能。

这时候热修复就是解决这种问题的最佳之选,既能修复问题,又能让用户无感知,两全其美。iOS热修复技术从最初的webView到最近的SOT,技术的发展越来越快,新技术已经到来:

一. 首先是原理篇MangoFix:(知道原理才能更好的干活)

热修复的核心原理:

  1. 拦截目标方法调用,让其调用转发到预先埋好的特定方法中

  2. 获取目标方法的调用参数

只要完成了上面两步,你就可以随心所欲了。在肆意发挥前,你需要掌握一些
Runtime 的基础理论,

Runtime 可以在运行时去动态的创建类和方法,因此你可以通过字符串反射的方式去动态调用OC方法、动态的替换方法、动态新增方法等等。下面简单介绍下热修复所需要用到的Runtime 知识点。

OC消息转发机制

descript

由上图消息转发流程图可以看出,系统给了3次机会让我们来拯救。

  1. 第一步,在resolveInstanceMethod方法里通过class_addMethod方法来动态添加未实现的方法;

  2. 第二步,在forwardingTargetForSelector方法里返回备用的接受者,通过备用接受者里的实现方法来完成调用;

  3. 第三步,系统会将方法信息打包进行最终的处理,在methodSignatureForSelector方法里可以对自己实现的方法进行方法签名,通过获取的方法签名来创建转发的NSInvocation对象,然后再到forwardInvocation方法里进行转发。

方法替换就利用第三步的转发进行替换。

当然现在有现成的,初级及以上iOS开发工程师很快就可以理解的语法分析,大概了解一下mangofix是可以转化oc和swift代码的:具体详情请看

MangoFix:iOS热修复另辟蹊径

那么为什么它可以执行转化呢,转化逻辑是什么?

MangoFix项目主页上中已经讲到,MangoFix既是一个iOS热修复SDK,但同时也是一门DSL(领域专用语言),即iOS热修复领域专用语言。既然是一门语言,那肯定要有相应的编译器或者解析器。相对于编译器,使用解析器实现语言的执行,虽然效率低了点,但显然更加简单和灵活,所以MangoFix选择了后者。下面我们先用一张简单流程图,看一下MangoFix的运行原理,然后逐一解释。

descript

1、MangoFix脚本

首先热修复之前,我们先要准备好热修复脚本文件,以确定我们的修复目标和执行逻辑,这个热修复脚本文件便是我们这里要介绍的MangoFix脚本,正常是放在我们的服务端,然后由App在启动时或者适当的运行期间进行下载,利用MangoFix提供的MFContext对象进行解析执行。对于MangoFix脚本的语法规则,这点可以参考MangoFix
Quick
Start,和OC的语法非常类似,你如果有OC开发经验,相信你花10分钟便可以学会。当然,在后续的文章中我可能也会介绍这一块。

2、词法分析器

几乎所有的语言都有词法分析器,主要是将我们的输入文件内容分割成一个个token,MangoFix也不例外,MangoFix词法分析器使用Lex所编写,如果你想了解MangoFix词法分析器的代码,可以点击这里。

3、语法分析器

和词法分析器类似,几乎所有语言也都有自己的语法分析器,其主要目的是将词法分析器输出的一个个token构建成一棵抽象语法树,而且这颗抽象语法树是符合我们预先设计好的上下文无关文法规则的,如果你想了解MangoFix语法分析器的代码,可以点击这里。

4、语义检查

由于语法分析器输出的抽象语法树,只是符合上下文无关文法规则,没有上下文语义关联,所以MangoFix还会进一步做语义检查。比如我们看下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface MyViewController : UIViewController

@end

class MyViewController : BaseViewController{

- (void)viewDidLoad{

*//TODO*

}

}

上面部分是OC代码,下面部分是MangoFix代码,从文法角度MangoFix这个代码是没有问题的,但是在逻辑上却有问题, MyViewController在原来OC中和MangoFix中继承的父类不一致,这是OC runtime所不允许的。

5、创建内置对象

MangoFix脚本中很多功能都是通过预先创建内置对象的方式支持的,比如常用结构体的声明、变量、宏、C函数和GCD相关的操作等,如果想详细了解MangoFix中有哪些内置对象,可以点击这里。当然MangoFix也开放了相关接口,你也可以向MangoFix执行上下文中注入你需要的对象。

6、执行顶层语句

在做完上面的操作后,MangoFix解析器就开
始真正执行MangoFix脚本了,比如顶层语句的执行、结构体的声明、类的定义等。

7、利用runtime热修复

现在就到了最关键一步了,就是利用runtime替换掉原来Method的IMP指针,MangoFix利用libffi库动态创建C函数,在创建的C函数中调用MangoFix脚本中方法,然后用刚刚创建的C函数替换原来Method的IMP指针,当然MangoFix也会保留原有的IMP指针,只不过这时候该IMP指针对应的selector要在原有的基础上在前面拼接上ORG,这一点和JSPatch一致。当然,MangoFix也支持对属性的添加。

8、MangoFix方法执行

最后当被修复的OC方法在被调用的时候,程序会走到我们动态创建的C函数中,在该函数中我们通过查找一个全局的方法替换表,找到对应的MangoFix方法实现,然后利用MangoFix解析器执行该MangoFix的方法。

二. 具体执行(OC修复OC)。

1.后台分发补丁平台:

补丁平台:http://patchhub.top/mangofix/login

github地址:https://github.com/yanshuimu/MangoFixUtil

1.首先你要明白:必须得有个后台去上传,分发bug的文件,安全起见,脚本已经通过AES128加密,终端收到加密的脚本再去解密,防止被劫持和篡改,造成代码出现问题。

登录这个补丁平台,可以快速创建appid。

github地址下载并配合使用:

以下是MangoFixUtil的说明:

MangoFixUtil是对MangoFix进行了简单的封装,该库在OC项目中实战已经近2年多,经过多次迭代,比较成熟。但需要搭配补丁管理后台一起使用,后台由作者开发维护,目前有50+个已上架AppStore的应用在使用,欢迎小伙伴们使用。

2.举个实战中的例子:

我们快速迭代中遇到的一些问题:

descript

有一次我们解析到后台数据从中间截取字符串,然而忘了做判空操作,后台数据一旦不给返回,那么项目立马崩溃,所以做了热修复demo.mg文件放到Patch管理平台,具体代码如OC基本一致:

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
class JRMineLoginHeaderView:JRTableViewHeaderView {

- (NSString *)getNetStringNnm:(NSString *)str{

NSError *error = nil;

if(str.length<=0) {

return @"";

}

NSRegularExpression *regex =
NSRegularExpression.regularExpressionWithPattern:options:error:(@"\d+",0,&error);

if (error) {

return @"";

} else {

if (str.length == 0) {

return @"";

}

NSArray *matches =
regex.matchesInString:options:range:(str,0,NSMakeRange(0, str.length));

for (NSTextCheckingResult *match in matches) {

NSString *matchString = str.substringWithRange:(match.range);

return matchString;

}

}

return @"";

}

}

以上代码中,新增了对象长度判空操作:

1
2
3
4
5
if(str.length<=0) {

return @"";

}

完美的解决了崩溃的问题。

2.oc转换成DSL语言。

一切准备就绪,oc转换成DSL语言浪费人力,而且准确率又低怎么办?怎么可以快速的用oc转换成mangofix语言呢?

这是macOS系统上的可视化辅助工具,将OC语言转成mangofix脚本。

做iOS热修复时,大量时间浪费在OC代码翻译成脚本上,提供这个辅助工具,希望能给iOSer提供便利,

本人写了一个mac应用,完美的解决了不同语法障碍,转换问题。

mac版本最低(macos10.11)支持内容:

  1. OC代码 一键 批量转换成脚本

  2. 支持复制.m内容粘贴,转换

  3. 支持单个OC API转换,自动补全

  4. 报错提示:根据行号定位到OC代码行

话不多说,上地址:

https://pub-13a255da812e460bb9571dad9bb2e123.r2.dev/OC2PatchTool2.dmg

3.打不开”OC2PatchTool.app”,因为它来自身份不明的开发者

  1. 方案1.系统偏好设置>>安全与隐私>>允许安装未知来源

  2. 方案2.打开 Terminal 终端后 ,在命令提示后输入

    1
    sudo spctl *--master-disable*

OC 转换成 脚本 支持两种方式

  • 方式1.拷贝.m文件所有内容,粘贴到OC输入框内。示例代码:AFHTTPSessionManager.m

    descript

  • 方式2. 拷贝某个方法粘贴到OC输入框内,转换时会自动补全

    descript

三.App 审核分析

其实能不能成功上线是热修复的首要前提,我们辛辛苦苦开的框架如果上不了线,那一切都是徒劳无功。下面就来分析下其审核风险。

  • 首先这个是通过大量C语言混编转换的,所以苹果审核无法通过静态代码识别,这一点是没有问题的。

  • 其次系统库内部也大量使用了消息转发机制。这一点可以通过符号断点验证_objc_msgForward和forwardInvocation:。所以不存在风险。此外,你还可以通过一些字符串拼接和base64编码方式进行混淆,这样就更加安全了。

  • 除非苹果采用动态检验消息转发,非系统调用都不能使用,但这个成本太大了,几乎不可能。

  • Mangofix 库目前线上有大量使用,为此不用担心。就算 Mangofix 被禁用,参考 Mangofix 自己开发也不难。

综上所述:超低审核风险。

热修复框架只是为了更好的控制线上bug影响范围和给用户更好的体验。

建议:

Hotfix会在应用程序运行时动态地加载代码,因此需要进行充分的测试,以确保修复的bug或添加的新功能不会导致应用程序崩溃或出现其他问题。

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道