一、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--支持