Mango 文档

一、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一致,但【不支持readonlyreadwrite】修饰

@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--支持
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271