一、Mango简介
Mango一种与Objective-C语法非常相似的语言脚本,也是一种iOS程序hotfix的执行方案,可以使用Mango方法替换任何Objective-C对象方法。
原理简单介绍
自定义的执行引擎Mango(包含词法、语法解析) 运行时动态替换hotifix中的API,真正hotfix代码执行时,对每一行脚本(DSL)代码解析执行。
和jspatch的唯一差别就是jspatch多一个JavaScriptCore执行引擎,这样很多非oc的代码,都可以再JavaScriptCore中运行,而mango中,非OC的代码需要在mango引擎中执行, OC的代码,两者都是反射执行。
性能说明
一点说明,mango脚本在运行时需要逐行解析执行,代码量特别大时,解析执行耗时较长。所以如果是有特别复杂/代码量特别大的代码块需要hotfix,务必在测试环境中多验证性能和稳定性。
针对这一点,我们随机选择了几个代码量相对较大的历史版本jspatch做测试,转换成mango脚本,性能都ok。
二、 Mango基本语法
支持的类型
Mango | OC/C |
---|---|
void | void |
BOOL | BOOL |
id | id |
Block | oc的block |
uint | unsigned char、unsigned short、unsigned int、unsigned long、unsigned long long、NSUInteger |
int | char、short、int、long、long long、NSInteger |
double | double、float、CGFloat |
除了以上之外,OC定义的类类型,可以直接在mango中使用
常用操作符
和OC基本一致,除了自增自减操作符部分不支持。
class ViewController:UIViewController {
- (void)sequentialStatementExample{
//变量定义
NSString *text = @"";
int a = 3.0;
double b = 2.0;
text += @"a = " + a + @"\n";
text += @"b = " + b + @"\n";
//加法运算
double c = a + b;
text += @"a + b = " + c + "\n";
//减法运算
double d = a - b;
text += @"a - b = " + d + "\n";
//乘法运算
double e = a * b;
text += @" a * b = " + e + "\n";
//除法运算
double f = a / b;
text += @"f = " + f + "\n";
//取模运算,只能操作整型变量
int g = a % 2;
text += @"a % 2 = " + g + "\n";
//+=运算
a += b;
text += @"a += b = " + a + @"\n";
//-=运算
a -= b;
text += @"a -= b = " + a + @"\n";
//*=运算
a *= b;
text += @"a *= b = " + a + @"\n";
// /=运算
a /= b;
text += @"a /= b = " + a + @"\n";
//%=运算
a %= 2;
text += @"a %= 2 = " + a + @"\n";
//三目运算
double h = a > b ? a : b;
text += @"a > b ? a : b = " + h + "\n";
//自增运算 不支持 ++a
a++;
text += @"a++ = " + a + "\n";
//自减运算 不支持 --a
a--;
text += @"a-- = " + a + "\n";
//数组操作,Mango对于泛型尚未支持
NSArray *arr = @[@"zhao", @"qian", @"sun", @"li"];
NSString *e2 = arr[2];
text += @"e2 = " + e2 + @"\n";
NSMutableArray *arrM = @[@"zhao", @"qian", @"sun", @"li"].mutableCopy();
arrM[2] = @"sun2";
e2 = arrM[2];
text += @"e2 = " + e2 + @"\n";
//字典操作
NSDictionary *dic = @{@"zhang":@"san",@"li":@"si",@"wang":@"wu",@"zhao":@"liu"};
NSString *liValue = dic[@"li"];
text += @"liValue = " + liValue + @"\n";
NSMutableDictionary *dicM = @{@"zhang":@"san",@"li":@"si",@"wang":@"wu",@"zhao":@"liu"}.mutableCopy();
dicM[@"li"] = @"si2";
liValue = dicM[@"li"];
text += @"liValue = " + liValue + @"\n";
self.resultView.text = text;
}
常用条件语句
和OC常用条件控制语句基本一致。
- (void)ifStatementExample{
int a = 2;
int b = 2;
NSString *text;
if(a > b){
text = @"执行结果: a > b";
}else if (a == b){
text = @"执行结果: a == b";
}else{
text = @"执行结果: a < b";
}
self.resultView.text = text;
}
- (void)switchStatementExample{
int a = 2;
NSString *text;
switch(a){
case 1:{
text = @"match 1";
break;
}
case 2:{} //case 后面的一对花括号不可以省略
case 3:{
text = @"match 2 or 3";
break;
}
case 4:{
text = @"match 4";
break;
}
default:{
text = @"match default";
}
}
self.resultView.text = text;
}
- (void)forStatementExample{
NSString *text = @"";
for(int i = 0; i < 20; i++){
text = text + i + @", ";
if(i == 10){ //if后面即使是单行语句,花括号也不可以省略
break;
}
}
self.resultView.text = text;
}
- (void)forEachStatementExample{
NSArray *arr = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"g", @"i", @"j",@"k"];
NSString *text = @"";
for(id element in arr){
text = text + element + @", ";
if(element.isEqualToString:(@"i")){
break;
}
}
self.resultView.text = text;
}
- (void)whileStatementExample{
int a;
while(a < 10){
if(a == 5){
break;
}
a++;
}
self.resultView.text = @""+a;
}
- (void)doWhileStatementExample{
int a = 0;
do{
a++;
}while(NO);
self.resultView.text = @""+a;
}
struct的使用
比较特殊,仅支持常用的 CGRect、 CGPoint、NSRange、CGSize几种类型。
//struct关键字必须要有
struct CGRect frame = CGRectMake(50, 100, 150, 200);
struct CGRect frame = {origin:{x:100,y:100},size:{width:100,height:100}}
struct CGSize size = CGSizeMake(100,100);
struct CGPoint point = {x:50,y:60};
struct NSRange range = {location:2,length:20};
struct CGRect frame = CGRectZero //crash,无常量CGRectZero
//不支持struct属性修改
frame.size = CGSizeMake(120,120);//无效
//无法实现与字符串转换
NSStringFromRange(range) //crash
CGRectFromString(@"{{100, 100}, {100, 100}}")//编译错误
三、 hotfix教程
指定需要修复的类
使用class关键字指明要fix的类 SubClasss : SuperClass
//举例:需要修复MainViewController中的API
class MainViewController:UIViewController {
...
}
覆盖类方法
方法声明同OC一致
class MainViewController:UIViewController {
//举例:重载MainViewController中的+(void)test:(NSString *)a方法
+(void)test:(NSString *)a{
...
}
}
覆盖实例方法
方法声明同OC一致
class MainViewController:UIViewController {
//举例:重载MainViewController中的-(void)test:(NSString *)a方法
-(void)test:(NSString *)a{
...
}
}
覆盖之后调用原来的方法
在方法名前添加ORIG即可调用被覆盖的方法之前的实现
class MainViewController:UIViewController {
-(void)test:(NSString *)a{
self.ORIGtest:(a); //调用原来的-(void)test:方法
MainViewController.ORIGtest(a); //调用原来的+(void)test:方法
}
}
调用OC方法
将原来的 [ ] 换成C类型 ( ) ,注意:方法中的 : 需要保留
OC : -(int)doSomething:(id)a { ... }
=>
Mango : self.doSomething:(a) //注意冒号需要保留
多参数调用
OC : -(void)customMethodParam1:(id)parma1 param2:(id)param2 {...}
=>
Mango : self.customMethodParam1:param2:(@"p1",@"p2");
函数链
UIView *view = UIView.alloc().initWithFrame:(CGRectMake(x, y, width, height));
view.backgroundColor = UIColor.whiteColor();
view.frame = CGRectMake(50, 100, 150, 200);
self.view.addSubview:(view);
Mango中Class相关方法扩展
在Mango中通过以下API判断Class的继承体系
- 判断当前类是否为指定类或其子类
BOOL isKind = obj.isKindOfClassByClassName:(@"ClassName")
- 判断指定className是否为另外一个className的类或其子类
BOOL a = NSObject.className:kindOfClassName:(@"subClassName",@"superClassName")
- 获取当前实例的className
NSString* name = obj.className()
- 判断指定className是否为指定类
BOOL a = obj.isMemberOfClassByClassName:(@"className")
- 判断指定className是否为另外一个className的类
BOOL a = NSObject.className:memberOfClassName:(@"subClassName",@"superClassName")
- 是否响应指定SELName
BOOL a = obj.respondsToSelectorByName:(@"SELName")
新增property
属性声明和用法与OC一致,但【不支持readonly、readwrite】修饰
@property (copy, nonatomic) NSString *testMainStr;
@property (strong, nonatomic) UIView *testView;
@property (weak, nonatomic) id testDelegate;
//使用
self.testMainStr = @"Mango Main Str";
特殊类型
struct
支持原生CGRect / CGPoint / CGSize / NSRange 这四个 struct 类型
struct CGRect frame = {origin:{x:100,y:100},size:{width:100,height:100}};
struct CGSize size = CGSizeMake(100,100);
struct CGPoint point = CGPointMake(20, 20);
struct NSRange range = NSMakeRange(1, 10);
range = {location:2,length:20};
selector
-(void)myExample{
}
SEL gcdsel = @selector(myExample);
GCD
-(void)gcdExample{
dispatch_queue_t queue = dispatch_queue_create("com.ctripdemo.mango", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"dispatch_async");
});
dispatch_sync(queue, ^{
NSLog(@"dispatch_sync");
});
}
nil和NULL
支持nil和NULL使用
self.pro1 = nil;
self.pro2 = NULL;
NSArray / NSString / NSDictionary
NSArray/NSDictionary/NSString 建议按照oc API使用
//字符串
id mangoValue = @"字符串开始" + 123 + @"结束";
//数组
NSArray *arr = @[@"zhao", @"qian", @"sun", @"li"];
for (int i = 0 ; i < arr.count; i ++){
NSString *arrVal = arr[i];
}
//字典
NSDictionary *dic = @{@"zhang":@"san",@"li":@"si",@"wang":@"wu",@"zhao":@"liu"};
NSMutableDictionary *mutableDic = NSMutableDictionary.dictionaryWithDictionary:(dic);
dic[@"newKey"] = @"newVal";
block
mango中使用Block关键字
//mango脚本中定义block
Block catStringBlock = ^NSString *(NSString *str1, NSString *str2){
NSString *result = str1.stringByAppendingString:(str2);
return result;
};
//使用block
NSString *result = catStringBlock(@"str1",@"str2");
//定义接收block参数的API
-(void)executeBlock:(Block)block {
NSString *value = block(@"val",@"val2");
}
// 使用带block参数的API
self.executeBlock:(catStringBlock);
mango定义block给OC
//例如OC中有如下接收block参数的API
-(void)fromMangoBlock:(NSString * (^)(NSString * str1,NSString * str2))block {
if(block){
NSString *str = block(@"block",@"lgq str");
NSLog(@"fromMangoBlock str = %@",str);
}
}
//mango脚本中定义block
Block catStringBlock = ^NSString *(NSString *str1, NSString *str2){
NSString *result = str1.stringByAppendingString:(str2);
return result;
};
//传递mango中定义的block到OC API
self.fromMangoBlock:(catStringBlock);
使用OC中定义的block
//例如OC代码定义以下block,并使用
id ocBlock = ^NSString *(NSString *str1){
return [str1 stringByAppendingString:@" mango"];
};
NSString *value = [self fromObjectC:@"https://xxx" block:ocBlock];
//Mango中需要使用ocBlock举例
-(NSString *)fromObjectC:(NSString *)url block:(id)block {
NSString *value = block(url + @" from ObjectC");
return value;
}
其他说明
- ==NSLog仅支持打印字符串, 不支持format字符串==
- ==不支持C函数,除了NSLog,如果有常用的C函数要用,需要定制==
- ==iOS 系统中定义的常量,不能直接使用,例如CGRectZero==
- 不支持通过@class去引用类,不需要import class,直接使用即可
- OC内置的Class结构图类型不支持,比如:obj.class()都不支持
属性不支持readonly、readwrite修饰符 - hotfix中新增class是支持的,但是新增的class不能被继承
- 不支持通过@protocol定义新的协议,实现已有协议是支持的
- 支持的struct类型,CGFrame CGSize CGPoint CGRange,声明 - 需要添加前缀struct,且无法单独修改属性
- 自增运算不支持++a,a++支持
- 自减运算不支持--a, a--支持