背景
移动端热修正是指在移动运用程序中对已发布的运用进行动态修正和更新,而无需用户从头下载安装新版本的运用。以下是一些研讨移动端热修正的原因:
-
修正运用程序缝隙和Bug:移动运用程序或许存在各种缝隙和Bug,这些问题或许会导致运用程序溃散、功用失效或数据走漏等问题。经过研讨移动端热修正技能,开发人员能够快速响应并修正这些问题,而无需用户等候和下载完好的运用程序更新。
-
供给快速更新和功用迭代:移动运用程序的开发和发布一般需求经过必定的时刻和审阅进程,这使得快速修正和更新运用程序变得困难。经过研讨移动端热修正技能,开发人员能够更快地推出新功用和修正问题,以满足用户的需求并供给更好的用户体会。
-
减少用户流失和进步用户满意度:当用户遇到运用程序中的问题或缺陷时,他们或许会感到不满并挑选卸载运用程序。经过运用移动端热修正技能,开发人员能够在最短时刻内修正问题,并避免用户因问题而离开运用程序,然后进步用户满意度和用户留存率。
-
下降运用程序保护本钱:传统的运用程序更新需求发布新版本,并要求用户下载和安装更新,这需求耗费开发人员和用户的时刻和资源。经过运用移动端热修正技能,能够减少运用程序的发布次数和用户的下载次数,然后下降运用程序的保护本钱。
总而言之,研讨移动端热修正能够供给一种快速、高效和经济的办法来修正移动运用程序中的问题,供给更好的用户体会,并下降开发和保护本钱。
痛点
移动端热修正的痛点首要有以下几个:
1.上架问题: 假如被商店查到运用此功用或许会面临被拒危险。
2.安全危险:热修正或许会导致运用程序的安全危险,由于攻击者或许会篡改热修正包,导致运用程序溃散或走漏敏感信息。
3.兼容性问题:热修正或许会引起运用程序的兼容性问题,由于新的代码或许与旧的代码不兼容,导致运用程序溃散或异常。
4.调试困难:热修正的代码一般比较难以调试,由于它是动态加载的,不容易在开发环境中调试。
5。功用问题:热修正或许会导致运用程序的功用问题,由于它需求动态加载代码,这或许会导致运用程序响应速度变慢或许增加内存占用。
6.保护存在必定本钱:热修正需求额定的保护本钱,由于需求开发额定的代码和测验热修正的效果。
因此,在运用移动端热修正技能时,需求仔细权衡各种利害,并采纳相应的安全措施来确保运用程序的安全性。一起,开发人员需求充沛考虑兼容性问题和功用问题,并进行充沛的测验和调试作业。
业界计划
jspatch 目前不能上架
JSPatch 是一个用于 iOS 运用的热修正框架,它允许开发者在不从头发布运用的情况下修正线上的 Bug 或许增加新功用。JSPatch 的原理能够简略归纳如下:
-
JSPatch 的运行环境是 JavaScriptCore,它是苹果供给的 JavaScript 引擎,能够在 iOS 运用中履行 JavaScript 代码。
-
在运用启动时,JSPatch 将下载的 JavaScript 脚本注入到 JavaScriptCore 中,并创立一个全局的 JavaScript 上下文。
-
JSPatch 运用 JavaScriptCore 供给的 Objective-C 与 JavaScript 交互的 API。开发者能够经过这些 API 将 Objective-C 方针和办法露出给 JavaScript,然后完成在 JavaScript 中调用原生的 Objective-C 办法。
-
JavaScript 脚本中的代码能够直接拜访运用中的 Objective-C 方针和办法,以及修正和履行这些方针和办法。
-
当开发者需求修正 Bug 或许增加新功用时,他们能够在 JavaScript 脚本中编写相应的逻辑。然后将这个脚本发布到服务器,并通知运用更新。
-
运用在后台经过网络恳求下载最新的 JavaScript 脚本,并将其注入到 JavaScriptCore 中。
-
下次运用启动时,注入的新 JavaScript 脚本将收效,并能够履行修正 Bug 或许增加新功用的逻辑。
总的来说,JSPatch 的原理是经过将 JavaScript 脚本注入到 iOS 运用中的 JavaScriptCore 中,完成在运行时修正 Bug 或许增加新功用。开发者能够经过 JavaScript 代码拜访和修正运用中的 Objective-C 方针和办法,然后完成动态修正和扩展运用的能力。
Ocrunner
是依据 mangofix做了优化,支撑直接运用oc语法做脚本,原理和mangofix 相同
mangofix
Mango源码剖析
目录结构
首要有 Compiler , Execute,libffi,symdl 这几个中心模块组成
Compiler
首要 yacc 词法,语法解析器,内置方针映射表,AST 解析后的内置方针相关的model类组成。
Execute
首要来接受yacc 处理后的后续操作,把对脚本解析后转换成相应的内置方针。
包含 MFContext履行的上下文 它用来履行整个热修正的 脚本初始化,加密,解密履行,以及解析入口等。
MangoFix脚本中很多功用都是经过预先创立内置方针的办法支撑的,比方常用结构体的声明、变量、宏、C函数和GCD相关的操作等,假如想详细了解MangoFix中有哪些内置方针,当然MangoFix也开放了相关接口,你也能够向MangoFix履行上下文中注入你需求的方针。
libffi
首要用来动态的履行某些函数办法。
libffi(The Foreign Function Interface library)是一个开源的软件库,它供给了一种通用的编程接口,用于调用不同编程言语之间的函数。它允许在运行时动态地调用和履行函数,而无需在编译时提前知道函数的签名或参数类型。
libffi的设计方针是为不同的编程言语供给一个统一的接口,使它们能够相互调用。它为许多常见的编程言语,如C、C++、Python、Ruby等供给了支撑,并能够方便地扩展到其他言语。
libffi的中心功用是动态地生成机器码来调用函数,这使得在运行时能够依据需求解析函数的签名、分配内存、处理参数传递和返回值等操作。它供给了一个高度灵活和可移植的接口,使得开发者能够轻松地在不同的编程环境中进行函数调用。
libffi的首要用途之一是在解说型言语中完成对外部库的调用。经过运用libffi,解说型言语的开发者能够方便地扩展言语的功用,调用本地代码库或其他言语编写的函数。它还能够用于完成动态链接,经过在运行时加载和履行共享库中的函数。
总的来说,libffi是一个十分有用的库,它供给了一种通用的办法来调用不同编程言语之间的函数,为开发者供给了更大的灵活性和互操作性。不管你是开发解说型言语、扩展其他编程言语功用,仍是需求完成动态链接,libffi都是一个值得考虑的挑选。
symdl是一个简略的小工具,它的功用与dlsym十分类似,运用symdl,能够传入动态链接的C函数名字符串,获取函数指针,然后完成对C函数的动态调用。
下面来看源码履行流程
首先来看下mango 脚本格式
关于脚本语法能够去相应地址检查
github.com/YPLiang19/M…
注意事项
一般来说在脚本是需求加密解密的,由于假如被他人动态的修正,或许会对app造成必定的影响。
1.对脚本进行ASE加密,此过程一般最后会上传到服务器,经过服务器下发。
NSError *outErr = nil;
BOOL writeResult = NO;
NSURL *scriptUrl = [[NSBundle mainBundle] URLForResource:@"demo" withExtension:@"mg"];
NSString *plainScriptString = [NSString stringWithContentsOfURL:scriptUrl encoding:NSUTF8StringEncoding error:&outErr];
if (outErr) goto err;
{
NSData *scriptData = [plainScriptString dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptedScriptData = [scriptData AES128ParmEncryptWithKey:aes128Key iv:aes128Iv];
NSString * encryptedPath= [(NSString *)[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"demo_encrypted.mg"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:encryptedPath]) {
[fileManager createFileAtPath:encryptedPath contents:nil attributes:nil];
}
writeResult = [encryptedScriptData writeToFile:encryptedPath options:NSDataWritingAtomic error:&outErr];
}
err:
if (outErr) NSLog(@"%@",outErr);
return writeResult;
}
2.context 初始化解密脚本,履行整个流程
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
BOOL writeResult = [self encryptPlainScirptToDocument];
if (!writeResult) {
return NO;
}
MFContext *context = [[MFContext alloc] initWithAES128Key:aes128Key iv:aes128Iv];
NSString * encryptedPath= [(NSString *)[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"demo_encrypted.mg"];
[NSURL URLWithString:@""];
NSURL *scriptUrl = [NSURL fileURLWithPath:encryptedPath];
[context evalMangoScriptWithURL:scriptUrl];
return YES;
}
evalMangoScriptWithAES128Data是解密并履行的中心办法。
- (void)evalMangoScriptWithURL:(NSURL *)url{
@autoreleasepool {
NSError *error;
NSData *encryptedData = [NSData dataWithContentsOfURL:url];
if (error) {
NSLog(@"[MangoFix] [ERROR] : %@",error);
return;
}
[self evalMangoScriptWithAES128Data:encryptedData];
}
}
- (void)evalMangoScriptWithAES128Data:(NSData *)scriptData {
@autoreleasepool {
NSData *mangoFixData = [scriptData AES128ParmDecryptWithKey:_key iv:_iv];
NSString *mangoFixString = [[NSString alloc] initWithData:mangoFixData encoding:NSUTF8StringEncoding];
if (!mangoFixString.length) {
NSLog(@"[MangoFix] [ERROR] : AES128(ECBMode) decrypt error!");
return;
}
mf_set_current_compile_util(self.interpreter);
mf_add_built_in(self.interpreter);
[self.interpreter compileSourceWithString:mangoFixString];
mf_set_current_compile_util(nil);
mf_interpret(self.interpreter);
}
}
几个中心过程
- mf_set_current_compile_util(self.interpreter);
- mf_add_built_in(self.interpreter);
- [self.interpreter compileSourceWithString:mangoFixString];
- mf_set_current_compile_util(nil);
- mf_interpret(self.interpreter);
1.mf_set_current_compile_util
会创立一个解析方针,经过 mf_set_current_compile_util 设置在关于线程里,估测是为了后续获取拜访。
2.mf_add_built_in
这段代码是为了向MFInterpreter方针增加内置功用,包含 结构体,办法,变量,gcd相关等。经过运用dispatch_once函数,这些内置操作只会在第一次调用mf_add_built_in函数时履行一次,确保增加内置功用的唯一性和线程安全性。
void mf_add_built_in(MFInterpreter *inter){
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
add_built_in_struct_declare();
add_build_in_function(inter);
add_build_in_var(inter);
add_gcd_build_in(inter);
});
}
3.compileSourceWithString 接下来开端进行相关解析作业
- (void)compileSourceWithString:(NSString *)source{
extern void mf_set_source_string(char const *source);
mf_set_source_string([source UTF8String]);
extern void yyrestart (FILE * input_file );
extern int yyparse(void);
yyrestart(NULL); /* 每次解析前,重置yylex */
if (yyparse()) {
return;
}
}
4.mf_set_source_string
是yacc + lex 的解析入口
5.经过了lex和 yacc 的处理后然后把内容放到了一个容器方针中,拿mf_add_class_definition来剖析
6.终究保存到了 topList 数组中
void mf_add_class_definition(MFClassDefinition *classDefinition){
MFInterpreter *interpreter = mf_get_current_compile_util();
interpreter.classDefinitionDic[classDefinition.name] = classDefinition;
[interpreter.topList addObject:classDefinition];
}
7.mf_interpret
终究经过 mf_interpret 遍历 topList 中的方针,经过runtime履行相应的操作,如动态增加办法 属性,交换办法等。终究经过 fix_class函数履行完好个流程,后续在程序动态履行的时候 会调用事先交换处理好的办法等。
void mf_interpret(MFInterpreter *interpreter){
for (__kindof NSObject *top in interpreter.topList) {
if ([top isKindOfClass:[MFStatement class]]) {
execute_statement(interpreter, interpreter.topScope, top);
}else if ([top isKindOfClass:[MFStructDeclare class]]){
add_struct_declare(interpreter,top);
}else if ([top isKindOfClass:[MFClassDefinition class]]){
define_class(interpreter, top);
fix_class(interpreter,top);
}
}
}
看几个要害 代码
关于功用:
关于功用,我没有详细剖析过,但是之前看到过一篇文章,对三种计划做过功用对比方下
功用:
设备: iPhone SE 2,iOS 14.3
在求斐波那契数列第25项的测验中:
- JSPatch: 履行时刻,均匀为 0.169 s。内存占用一向稳定在 12 MB 左右。
- OCRunner: 履行时刻,均匀为 1.05 s。内存占用,峰值为 60 MB 左右,其他稳定在 12 MB 左右。
- Mango: 履行时刻,均匀时刻为 2.38 s。内存占用,继续走高,最高的时候大约为 350 MB。
总结
我这里初衷也是对mangofix 运用流程和源码做一下简略的剖析,关于最后的挑选当然还要依据自身需求场景 和事务来决议是否运用哪一种,当然不管那种计划都是有必定危险的,期望能对大家有必定帮助。