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一致,但【不支持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--支持
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。