MangoFix is a DSL which syntax is very similar to Objective-C,MangoFix is also an iOS App hotfix SDK. You can use MangoFix method replace any Objective-C or Swift method.
MangoFix 学习讨论QQ群:766215773
MangoFix的收费版本已经发布,现在可以申请一个月的免费使用(单独联系766215773群主, QQ:76094586, 微信:yong_pliang) 收费版本新特性:
- 支持OC语法,代码可直接从Xcode copy
- 删除了libffi/lex/yacc等容易被拒的第三方库
- 已经对类名接口名做了简单混淆
- 一次性购买12个月赠送定制化混淆
- 支持可变参数和NSLog格式化打印
- bundleID前缀匹配版本每月499,完全匹配每月299
Mango
MangoFix is a DSL which syntax is very similar to Objective-C,MangoFix is also an iOS App hotfix SDK. You can use MangoFix method replace any Objective-C or Swift method (Support Swfit from MangoFxi 1.5).SWfit Example
import UIKit
import MangoFix
let aes128Key = "123456" let aes128Iv = "abcdef"
@main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let context = MFContext.init(aes128Key: aes128Key, iv: aes128Iv) let encryptedScriptURL = URL.init("Your URL") context.evalMangoScript(with: encryptedScriptURL) return true }
}
Objctive-C Example
#import "AppDelegate.h"
#import <MangoFix/MangoFix.h>
static NSString * const aes128Key = @"123456"; static NSString * const aes128Iv = @"abcdef";
@implementation AppDelegate
- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions {
MFContext *context = [[MFContext alloc] initWithAES128Key:aes128Key iv:aes128Iv];
NSURL *scriptUrl = [NSURL URLWithString:@"Your URL"];
[context evalMangoScriptWithURL:scriptUrl];
return YES;
}
@end
Installation
CocoaPods
- Add
pod 'MangoFix'to your Podfile. - Run
pod installorpod update. - Import
<MangoFix/MangoFix.h>
Usage
Objective-C
#import <MangoFix/MangoFix.h>exec Mango Script by [context evalMangoScriptWithSourceString:@""];
MFContext *context = [[MFContext alloc] init];
// exec mango file from network
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx/demo.mg"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse response, NSData data, NSError *connectionError) {
NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[context evalMangoScriptWithSourceString:script];
}];
// exec local mango file
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"mg"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[context evalMangoScriptWithSourceString:script];
Mango
Swift Quick start
/**
demo.mg
*/
// 给Swift中带模块名的MangoFixSwiftDylibTest.CustomView类,声明别名CustomView,必须先声明后使用 @SwiftClassAlias MangoFixSwiftDylibTest.CustomView CustomView;
// 修复MangoFixSwfitDemo模块中的ViewController类, 注意:@SwiftModule注解中的字符串参数时C字符串,不是OC字符串对象 @SwiftModule("MangoFixSwfitDemo") class ViewController:UIViewController {
- (void)sequentialStatementExample {
//变量定义
NSString *text = @"1";
self.resultView.text = text;
}
- (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){
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;
}
- (void)blockStatementExample {
Block catStringBlock = ^NSString (NSString str1, NSString *str2){
NSString *result = str1.stringByAppendingString:(str2);
return result;
};
NSString *result = catStringBlock(@"hello ", @"world!");
self.resultView.text = result;
}
// 当脚步中的方法名和Swift中方法名不一致时,利用MethodName注解,指明Swift中的方法名 @MethodName("swiftMethodParamPassingExampleWithBOOLArg:intArg:uintArg:blockArg:objArg:")
- (void)paramPassingExampleWithBOOLArg:(BOOL)BOOLArg intArg:(int) intArg uintArg:(NSUInteger)uintArg blockArg:(Block)blockArg objArg:(id)objArg {
NSString *text = @""; text += @"BOOLArg:" + BOOLArg + @",\n"; text += @"intArg:" + intArg + @",\n"; text += @"uintArg:" + uintArg + @",\n"; text += @"Block执行结果:" + blockArg(@"hello", @"mango") + @"\n"; text += @"objArg:" + objArg; self.resultView.text = text; }
@MethodName("paramPassingExampleWithStrutWithRect:")
- (struct CGRect)paramPassingExampleWithStrut:(struct CGRect)rect {
struct CGRect ret = rect; ret.origin.x = ret.origin.x + 1; ret.origin.y = ret.origin.y + 1; ret.size.width = ret.size.width + 1; ret.size.height = ret.size.height + 1; return ret; }
- (Block)returnBlockExample {
NSString *prefix = @"mango: ";
Block catStringBlock = ^NSString (NSString str1, NSString *str2){
NSString *result = str1.stringByAppendingString:(str2);
return prefix + result;
};
return catStringBlock;
}
- (void)callOriginalImp {
self.ORGcallOriginalImp();
}
- (void)createAndOpenNewViewControllerExample {
SubMyController *vc = SubMyController.alloc().init();
self.navigationController.pushViewController:animated:(vc,YES);
}
//类方法替换示例
- (void)classMethodExapleWithInstance:(ViewController *)vc {
vc.resultView.text = @"here is Mango Class Method " + self; }
//条件注释示例 @If($systemVersion.doubleValue() > 16.0 )
- (void)conditionsAnnotationExample {
self.resultView.text = @"here is Mango method"; }
//GCD示例
- (void)gcdExample {
dispatchqueuet queue = dispatchqueuecreate("com.plliang19.mango", DISPATCHQUEUECONCURRENT); dispatch_async(queue, ^{ NSLog(@"mango dispatch_async"); }); dispatch_sync(queue, ^{ NSLog(@"mango dispatch_sync"); }); }
- (void)staticVarAndGetVarAddressOperExample {
static int i = 0;
static dispatchoncet onceToken;
dispatch_once(&onceToken, ^{
i++;
});
self.resultView.text = @""+i;
}
- (void)cfuntionVarExample{
int NSDocumentDirectory = 9;
int NSUserDomainMask = 1;
int O_WRONLY = 0x0001; uint S_IRWXU = 0000700;
CFunction<id, int, int, BOOL> NSSearchPathForDirectoriesInDomains = CFunction("NSSearchPathForDirectoriesInDomains"); CFunction<int, char *, int, int> open = CFunction("open"); CFunction<sizet, int, void *, sizet> write = CFunction("write"); CFunction<int, int> close = CFunction("close");
NSString *doc = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *path = doc.stringByAppendingPathComponent:(@"MangoFxi.html"); NSFileManager *fileManager = NSFileManager.defaultManager(); if (!fileManager.fileExistsAtPath:(path)) { BOOL ret = fileManager.createFileAtPath:contents:attributes:(path, nil, nil); if (!ret) { self.resultView.text = @"创建文件失败"; return; } } int fd = open(path.UTF8String,OWRONLY, SIRWXU); if (fd < 0) { self.resultView.text = @"打开文件失败"; return; } NSURL *url = NSURL.URLWithString:(@"https://www.qq.com"); NSData *data = NSData.dataWithContentsOfURL:(url); write(fd, data.bytes, data.length); close(fd); self.resultView.text = @"文件写入成功:" + path; }
- (void)typedefExaple{
self.resultView.text = @"typedef long alias_long;";
}
}
int a = 1;
@SwiftModule("MangoFixSwiftDylibTest") class SuperMyController : UIViewController { /*
- (void)viewDidLoad {
super.viewDidLoad(); self.view.backgroundColor = UIColor.blueColor(); self.testMasonry(); } */
- (void)testMasonry {
UIView *superview = self.view;
UIView *view1 = UIView.alloc().init();
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = UIColor.greenColor();
superview.addSubview:(view1);
struct UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
view1.mas_makeConstraints:(^(MASConstraintMaker *make) {
make.top.equalTo()(superview.mas_top).with.offset()(padding.top); //with is an optional semantic filler
make.left.equalTo()(superview.mas_left).with.offset()(padding.left);
make.bottom.equalTo()(superview.mas_bottom).with.offset()(-padding.bottom);
make.right.equalTo()(superview.mas_right).with.offset()(-padding.right);
});
}
}
// 注意:其实此处父类前的@SwiftModule("MangoFixSwiftDylibTest")注解可以省略, 因为前面已经有声明SuperMyController是在MangoFixSwiftDylibTest模块中 class SubMyController : @SwiftModule("MangoFixSwiftDylibTest") SuperMyController {
@property (strong, nonatomic) UIView *rotateView;
- (void)viewDidLoad {
super.viewDidLoad();
self.title = @"Magno 创建自定义ViewController";
double width = 100;
double height = 100;
double x = self.view.frame.size.width/2 - width/2;
double y = self.view.frame.size.height/2 - height/2;
UIView *view = CustomView.alloc().initWithFrame:(CGRectMake(x, y, width, height));
self.view.addSubview:(view);
view.backgroundColor = UIColor.redColor();
self.rotateView = view;
UILabel *label = UILabel.alloc().initWithFrame:(CGRectMake(10, 80, 350, 600));
label.numberOfLines = 100;
label.text =@" 长文本测试 : select()机制中提供一fdset的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fdset的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。select()机制中提供一fdset的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fdset的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。select()机制中提供一fdset的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fdset的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。select()机制中提供一fdset的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fdset的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。";
self.view.addSubview:(label);
__weak id weakSelf = self; self.block = ^{ __strong ids strongSelf = weakSelf; NSLog(strongSelf.class()); };
UIButton *btn = UIButton.alloc().initWithFrame:(CGRectMake(100, 300, 200, 50)); btn.setBackgroundColor:(UIColor.redColor()); btn.setTitle:forState:(@"test btn click", UIControlStateNormal); btn.addTarget:action:forControlEvents:(self, @selector(btnDidClicked:), UIControlEventTouchUpInside); self.view.addSubview:(btn);
}
- (void)btnDidClicked:(id)btn {
NSLog(btn);
}
}
Objective-C Quick start
/**
demo.mg
*/
//声明一个自定义结构体 declare struct MyStruct { typeEncoding:"{MyStruct=dd}", keys:x,y }
class ViewController:UIViewController {
- (void)sequentialStatementExample{
//变量定义
NSString *text = @"1";
self.resultView.text = text;
}
- (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){
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;
}
- (void)blockStatementExample{
Block catStringBlock = ^NSString (NSString str1, NSString *str2){
NSString *result = str1.stringByAppendingString:(str2);
return result;
};
NSString *result = catStringBlock(@"hello ", @"world!");
self.resultView.text = result;
}
- (void)paramPassingExampleWithBOOLArg:(BOOL)BOOLArg intArg:(int) intArg uintArg:(NSUInteger)uintArg blockArg:(Block)blockArg objArg:(id)objArg {
NSString *text = @"";
text += @"BOOLArg:" + BOOLArg + @",\n";
text += @"intArg:" + intArg + @",\n";
text += @"uintArg:" + uintArg + @",\n";
text += @"Block执行结果:" + blockArg(@"hello", @"mango") + @"\n";
text += @"objArg:" + objArg;
self.resultView.text = text;
}
- (struct MyStruct)paramPassingExampleWithStrut:(struct CGRect)rect{
struct MyStruct myStruct = {x:(rect.origin.x + 100), y:(rect.origin.x + 10)};
return myStruct;
}
- (Block)returnBlockExample{
NSString *prefix = @"mango: ";
Block catStringBlock = ^NSString (NSString str1, NSString *str2){
NSString *result = str1.stringByAppendingString:(str2);
return prefix + result;
};
return catStringBlock;
}
- (void)callOriginalImp{
self.ORGcallOriginalImp();
}
- (void)createAndOpenNewViewControllerExample{
SubMyController *vc = SubMyController.alloc().init();
self.navigationController.pushViewController:animated:(vc,YES);
}
//类方法替换示例
- (void)classMethodExapleWithInstance:(ViewController *)vc{
vc.resultView.text = @"here is Mango Class Method " + self; }
//条件注释示例 #If($systemVersion.doubleValue() > 12.0 )
- (void)conditionsAnnotationExample{
self.resultView.text = @"here is Mango method"; }
//GCD示例
- (void)gcdExample{
dispatchqueuet queue = dispatchqueuecreate("com.plliang19.mango", DISPATCHQUEUECONCURRENT); dispatch_async(queue, ^{ NSLog(@"mango dispatch_async"); }); dispatch_sync(queue, ^{ NSLog(@"mango dispatch_sync"); }); }
//静态变量和取地址运算符示例
- (void)staticVarAndGetVarAddressOperExample{
static int i = 0; static dispatchoncet onceToken; dispatch_once(&onceToken, ^{ i++; }); self.resultView.text = @""+i; }
//C函数变量示例
- (void)cfuntionVarExample{
int NSDocumentDirectory = 9; int NSUserDomainMask = 1;
int O_WRONLY = 0x0001; uint S_IRWXU = 0000700;
CFunction<id, int, int, BOOL> NSSearchPathForDirectoriesInDomains = CFunction("NSSearchPathForDirectoriesInDomains"); CFunction<int, char *, int, int> open = CFunction("open"); CFunction<sizet, int, void *, sizet> write = CFunction("write"); CFunction<int, int> close = CFunction("close");
NSString *doc = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *path = doc.stringByAppendingPathComponent:(@"MangoFxi.html"); NSFileManager *fileManager = NSFileManager.defaultManager(); if (!fileManager.fileExistsAtPath:(path)) { BOOL ret = fileManager.createFileAtPath:contents:attributes:(path, nil, nil); if (!ret) { self.resultView.text = @"创建文件失败"; return; } } int fd = open(path.UTF8String,OWRONLY, SIRWXU); if (fd < 0) { self.resultView.text = @"打开文件失败"; return; } NSURL *url = NSURL.URLWithString:(@"https://github.com/YPLiang19/Mango"); NSData *data = NSData.dataWithContentsOfURL:(url); write(fd, data.bytes, data.length); close(fd); self.resultView.text = @"文件写入成功:" + path; }
// Native 全局变量访问测试
- (void)nativeGlobalVariableAccess {
extern int nativeInt1; extern int nativeInt2; NSLog(@"--" + nativeInt1); NSLog(@"--" + nativeInt2);
nativeInt2 = nativeInt1; NSLog(@"--" + nativeInt1); NSLog(@"--" + nativeInt2);
CFunction<void, char *> testNativeCStringFunc = CFunction("testNativeCStringFunc"); extern Pointer nativeCString1; extern Pointer nativeCString2; testNativeCStringFunc(nativeCString1); testNativeCStringFunc(nativeCString2); nativeCString2 = nativeCString1; testNativeCStringFunc(nativeCString1); testNativeCStringFunc(nativeCString2);
extern NSString *nativeNSString; self.resultView.text = nativeNSString; }
//typedef示例
- (void)typedefExaple{
self.resultView.text = @"typedef long alias_long;"; }
}
class SuperMyController:UIViewController{
- (void)viewDidLoad {
super.viewDidLoad(); self.view.backgroundColor = UIColor.blueColor(); }
}
class SubMyController:SuperMyController { @property (strong, nonatomic) UIView *rotateView;
- (void)viewDidLoad {
super.viewDidLoad(); self.title = @"Magno 创建自定义ViewController"; double width = 100; double height = 100; double x = self.view.frame.size.width/2 - width/2; double y = self.view.frame.size.height/2 - height/2; UIView *view = MyView.alloc().initWithFrame:(CGRectMake(x, y, width, height)); self.view.addSubview:(view); view.backgroundColor = UIColor.redColor(); self.rotateView = view; }
}
class WebKitViewController : UIViewController {
- (void)viewDidLoad {
super.viewDidLoad();
self.view.backgroundColor = UIColor.redColor();
WKWebView *webView = WKWebView.alloc().initWithFrame:(self.view.bounds);
self.view.addSubview:(webView);
webView.navigationDelegate = self;
NSURL *url = NSURL.URLWithString:(@"https://www.baidu.com");
NSURLRequest *request = NSURLRequest.requestWithURL:(url);
webView.loadRequest:(request);
}
- (void)webView:(WKWebView )webView decidePolicyForNavigationAction:(WKNavigationAction )navigationAction decisionHandler:(@Signature("v16@?0q8") Block)decisionHandler {
NSLog(@"decidePolicyForNavigationAction");
int WKNavigationActionPolicyAllow = 1;
decisionHandler(WKNavigationActionPolicyAllow);
}
}
Mango Type usage
Mango support type as fllow:void
equivalent to Objective-Cvoid.
BOOL
equivalent to Objective-CBOOL.
uint
equivalent to Objective-Cunsigned char、unsigned short、unsigned int、unsigned long、unsigned long long、NSUInteger.
int
equivalent to Objective-Cchar、short、int、long、long long、NSInteger.
double
equivalent to Objective-Cdouble、float、CGFloat.
id
equivalent to Objective-Cid.