一、基本使用
MangoFix 是 iOS 中另一个热修复 SDK,其实现原理与 JSPatch 不同。MangoFix 定义了一套与 OC 相似语法规则,使用该规则编写热修复代码,并使用 Lex、Yacc 实现词法分析器、语法分析器处理热修复代码生成抽象语法树 AST 并解释执行,再利用 Runtime 实现方法的替换、新增等,以达到热修复的目的。
例如,想使用 MangoFix 热修复修改 ViewController
中的 testMethodWithStr:
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @interface ViewController : UIViewController
- (void)testMethodWithStr:(NSString *)str;
@end
@implementation ViewController
- (void)testMethodWithStr:(NSString *)str { }
@end
|
热修复代码文件(demo.mg)中热修复代码如下:
1 2 3 4 5 6 7 8 9
| class ViewController:UIViewController {
- (void)testMethodWithStr:(NSString *)str { NSString *text = @"Example Text"; self.resultView.text = text; }
}
|
加载热修复代码文件:
1 2 3 4 5
| MFContext *context = [[MFContext alloc] init]; NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"mg"]; NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; [context evalMangoScriptWithSourceString:script];
|
二、实现原理
无论是 JSPatch 还是 MangoFix,热修复代码本质上都是一段代码字符串,要实现热修复,最关键的一步就是如何让这段代码字符串被 iOS 系统(或 APP)理解并执行。
JSPatch 利用了 iOS 系统提供的 JavaScriptCore 的 API,通过使用 JS 编写热修复代码,利用 JavaScriptCore 去执行 JS 代码(字符串),最后利用 Runtime 去实现方法的替换,达到了热修复的目的。
而 MangoFix 则是自定义了一套语言,这套语言仅可用于 iOS 的热修复,所以可以被称为 DSL(Domain-Specific Language,领域特定语言)。为了使 MangoFix 的这套自定义的语言编写的可以被执行,就需要将 MangoFix 热修复代码字符串构建成一个逻辑能被 iOS 系统(或 APP)识别、执行和遍历的抽象语法树 AST。
而要构建 AST,就必须实现对应的词法分析器、语法分析器。MangoFix 则利用 Lex、Yacc 分别实现词法分析器、语法分析器,最终使热修复代码字符串得以被执行,最后再利用 Runtime 实现对原方法的替换。
简而言之,MangoFix 自定义了一套解释型语言用于编写热修复代码,再利用 Lex、Yacc实现解释器并执行热修复代码,最后再利用 Runtime 实现对原方法的替换,达到热修复的目的。
还以下面这段热修复代码为例看下 MangoFix 实现原理:
1 2 3 4 5 6 7 8 9
| class ViewController:UIViewController {
- (void)testMethodWithStr:(NSString *)str { NSString *text = @"Example Text"; self.resultView.text = text; }
}
|
MangoFix 加载执行热修复代码,主要是通过下述方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| - (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); } }
|
这里主要关注 compileSourceWithString:
和 mf_interpret()
两个方法。
compileSourceWithString:
方法实现如下:
1 2 3 4 5 6 7 8 9 10
| - (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); if (yyparse()) { return; } }
|
可以看到该方法主要逻辑如下:
- 设置词法分析源字符串为热修复代码字符串
- 重置词法分析器
- 执行语法分析函数
yyparse()
其中,yyparse()
函数是语法分析器核心函数,是 Xcode 根据 Yacc 文件(MangoFix/Compiler/lex_yacc/mango.y)自动编译生成的。
关于 Lex 词法分析器、Yacc 语法分析器相关内容,可以看我的另一篇博客:《编译原理之 Lex & Yacc》
前面提到的热修复示例代码:
1 2 3 4 5 6 7 8 9
| class ViewController:UIViewController {
- (void)testMethodWithStr:(NSString *)str { NSString *text = @"Example Text"; self.resultView.text = text; }
}
|
在词法分析、语法分析时,就需要匹配出代码字符串中的类、父类、方法名、方法实现等内容。
以类信息匹配为例,Yacc 文件(mango.y)中定义的类匹配文法规则如下:
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
| class_definition: annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LC { NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1; NSString *name = (__bridge_transfer NSString *)$3; NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5; NSString *superName = (__bridge_transfer NSString *)$6; mf_start_class_definition(annotationList, name, superAnnotationList, superName,nil); } RC { MFClassDefinition *classDefinition = mf_end_class_definition(nil); $$ = (__bridge_retained void *)classDefinition; } | annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LC { NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1; NSString *name = (__bridge_transfer NSString *)$3; NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5; NSString *superName = (__bridge_transfer NSString *)$6; mf_start_class_definition(annotationList, name, superAnnotationList, superName,nil); } member_definition_list RC { NSArray *members = (__bridge_transfer NSArray *)$9; MFClassDefinition *classDefinition = mf_end_class_definition(members); $$ = (__bridge_retained void *)classDefinition; } | annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LT protocol_list GT LC { NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1; NSString *name = (__bridge_transfer NSString *)$3; NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5; NSString *superName = (__bridge_transfer NSString *)$6; NSArray *protocolNames = (__bridge_transfer NSArray *)$8; mf_start_class_definition(annotationList, name, superAnnotationList, superName, protocolNames); } RC { MFClassDefinition *classDefinition = mf_end_class_definition(nil); $$ = (__bridge_retained void *)classDefinition; } | annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LT protocol_list GT LC { NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1; NSString *name = (__bridge_transfer NSString *)$3; NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5; NSString *superName = (__bridge_transfer NSString *)$6; NSArray *protocolNames = (__bridge_transfer NSArray *)$8; mf_start_class_definition(annotationList, name, superAnnotationList, superName, protocolNames); } member_definition_list RC { NSArray *members = (__bridge_transfer NSArray *)$12; MFClassDefinition *classDefinition = mf_end_class_definition(members); $$ = (__bridge_retained void *)classDefinition; } ;
|
上述主要逻辑如下:
- 词法分析器输出词法单元 tokens
- 语法分析器使用 tokens 按照类文法规则匹配类信息
- 匹配成功后,获取类、父类等信息,调用
mf_start_class_definition()
函数
- 匹配结束后,调用
mf_end_class_definition()
函数
mf_start_class_definition()
和 mf_end_class_definition()
函数实现如下:
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
| void mf_start_class_definition(NSArray<MFAnnotation *> *annotationList, NSString *name, NSArray<MFAnnotation *> *superAnnotationList, NSString *superName, NSArray<NSString *> *protocolNames){ MFInterpreter *interpreter = mf_get_current_compile_util(); MFClassDefinition *classDefinition = [[MFClassDefinition alloc] init]; classDefinition.lineNumber = interpreter.currentLineNumber; classDefinition.annotationList = annotationList; classDefinition.superAnnotationList = superAnnotationList; if (classDefinition.swiftModuleAnnotation) { classDefinition.name = [NSString stringWithFormat:@"%s.%@", classDefinition.swiftModuleAnnotation.expr.cstringValue, name]; [[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:classDefinition.name alias:name]; } else { NSString *swiftClassName = [[MFSwfitClassNameAlisTable shareInstance] swiftClassNameByAlias:name]; if (swiftClassName) { classDefinition.name = swiftClassName; } else { classDefinition.name = name; } } if (classDefinition.superSwiftModuleAnnotation) { classDefinition.superName = [NSString stringWithFormat:@"%s.%@", classDefinition.superSwiftModuleAnnotation.expr.cstringValue, superName]; [[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:classDefinition.superName alias:superName]; } else { NSString *swiftSuperClassName = [[MFSwfitClassNameAlisTable shareInstance] swiftClassNameByAlias:superName]; if (swiftSuperClassName) { classDefinition.superName = swiftSuperClassName; } else { classDefinition.superName = superName; } } classDefinition.protocolNames = protocolNames; interpreter.currentClassDefinition = classDefinition; }
MFClassDefinition *mf_end_class_definition(NSArray<MFMemberDefinition *> *members){ MFInterpreter *interpreter = mf_get_current_compile_util(); MFClassDefinition *classDefinition = interpreter.currentClassDefinition; NSMutableArray<MFPropertyDefinition *> *propertyDefinition = [NSMutableArray array]; NSMutableArray<MFMethodDefinition *> *classMethods = [NSMutableArray array]; NSMutableArray<MFMethodDefinition *> *instanceMethods = [NSMutableArray array]; for (MFMemberDefinition *memberDefinition in members) { memberDefinition.classDefinition = classDefinition; if ([memberDefinition isKindOfClass:[MFPropertyDefinition class]]) { [propertyDefinition addObject:(MFPropertyDefinition *)memberDefinition]; }else if ([memberDefinition isKindOfClass:[MFMethodDefinition class]]){ MFMethodDefinition *methodDefinition = (MFMethodDefinition *)memberDefinition; if (methodDefinition.classMethod) { [classMethods addObject:methodDefinition]; }else{ [instanceMethods addObject:methodDefinition]; } } } classDefinition.properties = propertyDefinition; classDefinition.classMethods = classMethods; classDefinition.instanceMethods = instanceMethods; interpreter.currentClassDefinition = nil; return classDefinition; }
|
mf_start_class_definition()
主要逻辑如下:
- 获取当前的解释器实例
- 创建一个新的
MFClassDefinition
对象,并初始化其属性
- 将解释器当前行号赋值给类定义对象的
lineNumber
属性(用于后续的异常信息中)
- 将
MFClassDefinition
对象保存到解释器的 currentClassDefinition
属性中
mf_end_class_definition()
主要逻辑如下:
- 获取当前解释器实例
- 获取解释器的
currentClassDefinition
- 将从热修复代码中匹配到的属性、类方法、实例方法分别保存到
currentClassDefinition
的 properties
、classMethods
和 instanceMethods
中
除了上述类信息匹配之外,还有对应的方法信息、方法实现等信息的匹配,匹配完成后,就生成了对应的 AST,而上述的 MFClassDefinition
则是 AST 的一个节点,其中就保存了热修复代码中的类、方法、属性等信息。
生成的 AST 之后,就开始执行 mf_interpret()
函数,mf_interpret()
函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12
| 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); } } }
|
在前述的示例中,创建的 AST 的 top 节点是 MFClassDefinition ,所以此处会调用 define_class()
和 fix_class()
函数。
define_class()
实现如下:
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 60 61 62 63 64 65 66 67 68 69 70 71
| static void define_class(MFInterpreter *interpreter, MFClassDefinition *classDefinition){ if (classDefinition.annotationIfExprResult == AnnotationIfExprResultNoComputed) { MFExpression *annotationIfConditionExpr = classDefinition.ifAnnotation.expr; if (annotationIfConditionExpr) { MFValue *value = mf_eval_expression(interpreter, interpreter.topScope, annotationIfConditionExpr); classDefinition.annotationIfExprResult = value.isSubtantial ? AnnotationIfExprResultTrue : AnnotationIfExprResultFalse; if (!value.isSubtantial) { return; } }else{ classDefinition.annotationIfExprResult = AnnotationIfExprResultTrue; } } if (classDefinition.annotationIfExprResult != AnnotationIfExprResultTrue) { return; } Class clazz = NSClassFromString(classDefinition.name); if (!clazz) { NSString *superClassName = classDefinition.superName; Class superClass = NSClassFromString(superClassName); if (!superClass) { if (classDefinition.swiftModuleAnnotation && !classDefinition.superSwiftModuleAnnotation) { NSString *sueprClassFullName = [NSString stringWithFormat:@"%s.%@", classDefinition.swiftModuleAnnotation.expr.cstringValue, superClassName]; superClass = NSClassFromString(sueprClassFullName); if (superClass) { [[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:sueprClassFullName alias:superClassName]; classDefinition.superName = sueprClassFullName; } } } if (!superClass) { define_class(interpreter, interpreter.classDefinitionDic[superClassName]); superClass = NSClassFromString(superClassName); } if (!superClass && classDefinition.swiftModuleAnnotation && !classDefinition.superSwiftModuleAnnotation) { NSString *sueprClassFullName = [NSString stringWithFormat:@"%s.%@", classDefinition.swiftModuleAnnotation.expr.cstringValue, superClassName]; define_class(interpreter, interpreter.classDefinitionDic[sueprClassFullName]); superClass = NSClassFromString(sueprClassFullName); if (superClass) { [[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:sueprClassFullName alias:superClassName]; classDefinition.superName = sueprClassFullName; } } if (!superClass) { mf_throw_error(classDefinition.lineNumber, MFRuntimeErrorNotFoundSuperClass, @"not found super class: %@",superClassName); return; } clazz = objc_allocateClassPair(superClass, classDefinition.name.UTF8String, 0); objc_registerClassPair(clazz); } classDefinition.clazz = clazz; }
|
define_class()
主要逻辑如下:
- 获取类名对应的
Class
对象,判断 Class
对象是否已存在。
- 如果不存在,则动态创建
Class
。
- 如果已经存在,直接保存到
classDefinition
的 clazz
属性中。
fix_class()
实现如下:
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| static void fix_class(MFInterpreter *interpreter, MFClassDefinition *classDefinition){ Class clazz = classDefinition.clazz; for (MFPropertyDefinition *prop in classDefinition.properties) { replace_prop(interpreter,clazz, prop); } for (MFMethodDefinition *classMethod in classDefinition.classMethods) { replace_method(interpreter, clazz, classMethod); } for (MFMethodDefinition *instanceMethod in classDefinition.instanceMethods) { replace_method(interpreter, clazz, instanceMethod); } }
static void replace_method(MFInterpreter *interpreter, Class clazz, MFMethodDefinition *method){
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:typeEncoding.UTF8String]; unsigned int argCount = (unsigned int)[sig numberOfArguments]; void *imp = NULL; ffi_cif *cif = malloc(sizeof(ffi_cif)); ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), (void **)&imp); ffi_type *returnType = mf_ffi_type_with_type_encoding(sig.methodReturnType); ffi_type **args = malloc(sizeof(ffi_type *) * argCount); for (int i = 0 ; i < argCount; i++) { args[i] = mf_ffi_type_with_type_encoding([sig getArgumentTypeAtIndex:i]); }
if(ffi_prep_cif(cif, FFI_DEFAULT_ABI, argCount, returnType, args) == FFI_OK) { NSDictionary *userInfo = nil; if (method.classDefinition) { userInfo = @{@"class":c2, @"typeEncoding":[typeEncoding copy], @"classDefinition" : method.classDefinition}; } else { userInfo = @{@"class":c2, @"typeEncoding":[typeEncoding copy]}; } CFTypeRef cfuserInfo = (__bridge_retained CFTypeRef)userInfo; ffi_prep_closure_loc(closure, cif, replaceIMP, (void *)cfuserInfo, imp); } class_replaceMethod(c2, sel, imp, typeEncoding.UTF8String); }
static void replaceIMP(ffi_cif *cif, void *ret, void **args, void *userdata){
NSMutableArray<MFValue *> *argValues = [NSMutableArray array]; NSUInteger numberOfArguments = [methodSignature numberOfArguments]; for (NSUInteger i = 0; i < numberOfArguments; i++) { MFValue *argValue; const char *type = [methodSignature getArgumentTypeAtIndex:i]; if (strcmp(type, "@?") == 0) { id block = (__bridge id)(*(void **)args[i]); block = [block copy]; argValue = [MFValue valueInstanceWithObject:block]; }else{ void *arg = args[i]; argValue = [[MFValue alloc] initWithCValuePointer:arg typeEncoding:[methodSignature getArgumentTypeAtIndex:i] bridgeTransfer:NO]; } [argValues addObject:argValue]; } __autoreleasing MFValue *retValue = mf_call_mf_function(inter, classScope, method.functionDefinition, argValues); [retValue assignToCValuePointer:ret typeEncoding:[methodSignature methodReturnType]]; }
|
fix_class()
主要逻辑如下:
- 创建
imp
,用于关联热修复代码中方法实现
- 利用 libffi 将
replaceIMP
函数与 imp
进行关联
- 利用 Runtime 使用
imp
替换 OC 原方法实现
- 完成替换后,原方法执行,实际将会执行到
replaceIMP
函数
replaceIMP
函数中,会传递方法参数并执行热修复代码中具体方法逻辑
关于 libffi:
libffi(Foreign Function Interface Library)是一个开源库(libffi),它提供了最底层的接口,在不确定参数个数和类型的情况下,根据相应规则,完成所需数据的准备,生成相应汇编指令的代码来完成函数调用。
libffi 允许程序在运行时调用未知的函数,主要用于实现跨语言调用和动态代码生成,它在运行时生成调用代码,而不需要在编译时知道函数的签名。libffi 在很多编程语言的实现中都有应用,例如 Python 的 ctypes 模块、Java 的 JNI(Java Native Interface)、和 Ruby 的 Fiddle 模块等。
总结上述逻辑,MangoFix 实现方法替换的流程可用下图表示: