MangoFix:iOS热修复另辟蹊径

  今天向大家介绍的是iOS热修复的另一解决方案:MangoFix。介绍他的原因是他和传统的iOS热修复使用JavaScript bridge 的方式完全不同,MangoFix是一个语法和OC语法非常类似的DSL,其语言本身的设计目标就是为了解决iOS热修复问题,所以在使用的便捷程度和性能方面都要远远超过传统的iOS 热修复SDK,比如JSPatch。下面从以下几点介绍MangoFix,更具体的请参考GitHub文档和MangoFix单元测试,也可以加入MangoFix的QQ讨论群:766215773。

1、如何加载一个MangoFix脚本

  • 1 首先通过CocoaPods安装MangoFix : pod 'MangoFix'
  • 2 引入MangoFix头文件:#import <MangoFix/MangoFix.h>
  • 3 创建MangoFix脚本执行上下文对象MFContext实例
  • 4 运行MangoFix脚本文件
    示例代码如下:
    NSString *path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"mg"];
    NSURL *scriptUrl = [NSURL fileURLWithPath:path];
    MFContext *context = [[MFContext alloc] init];
    [context evalMangoScriptWithURL:scriptUrl];

2、MangoFix如何修复OC对象(类)方法

  MangoFix可以替换或创建任意OC对象实例方法或类方法,语法和OC类似,不过在类的定义上采用class关键字。下面示例:

class MFInstanceMethodReplaceTest : NSObject {
  
- (BOOL)testInstanceMethodReplace{
    return YES;
}
    
}

对于类方法的替换只需将方法返回值类型前的-修改为+即可。
需要注意的是:
1、继承的父类不可以省略。

3、MangoFix如何为对象添加属性

  MangoFix中为对象添加属性和OC一样,支持的修饰符有: weakstrongcopyassignnonatomicatomic。下面看一下示例代码:

class MFObjectPropertyTest : NSObject{

@property(nonatomic, copy)NSString *propertyName;
  
- (NSString *)testObjectPropertyTest{
    return self.strTypeProperty;
}

}

需要注意的是:
1、属性不支持class修饰符。
2、MangoFix是通过objc_setAssociatedObject实现属性值的存储,但是MangoFix在解析时候做了处理,访问属性值也可以通过_propertyName这种方式进行访问。

4、MangoFix中如何使用block

  在MangoFix对OC中block类型声明过于复杂做了简化,用Block关键字表示block类型,block的定义则和OC相同,示例代码如下:


class MFMethodParameterListAndReturnValueTest : NSObject{

- (Block)testMethodParameterListAndReturnValueWithString:(NSString *)str block:(Block)block{
    NSMutableDictionary *dic = @{}.mutableCopy();
    dic[@"param1"] = str + @"MangoFix";
    dic[@"param2"] = block(@"MangoFix");
    
    Block retBlock = ^NSDictionary *(/*不能加void*/){
        return dic;
    };
    return retBlock;
}

}

需要注意的是:
1、在无参block定义时,不可以加void声明。
2、Block关键字后面不需要加*运算符。

5、如何解决Block循环引用问题

   MangoFix在1.1.7版本中添加__weak__strong变量修饰符,可以像OC原生一样解决Block循环引用问题,使用示例如下:

@interface MyController : UIViewController

@property(nonatomic,copy) id block;

@end
class MyController: UIViewController {
- (void)viewDidLoad {
        super.viewDidLoad();
        __weak id weakSelf = self;
        self.block = ^{
            __strong id strongSelf = weakSelf;
            NSLog(weakSelf);
        };
}

}

上部分是OC代码,下部分是MangoFix代码,需要注意的是,__weak__strong只能放在变量类型之前。

6、MangoFix中如何使用GCD

  MangoFix中已经内置的GCD API,使用方法和 OC相同,对于需要扩展的C函数,可以参考下面如何在MangoFix中注入全局对象的描述,GCD使用示例如下:


class MFGCDTest : NSObject {

- (void)testGCDWithCompletionBlock:(Block)completion{
    dispatch_queue_t queue = dispatch_queue_create("com.plliang19.mango", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        completion(@"success");
    });
}

- (void)testGCDAfterWithCompletionBlock:(Block)completion{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        completion(@"success");
    });
}
    
}


class MFDispatchSourceTest : NSObject{

- (NSInteger)testDispatchSource{
    NSInteger count = 0;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        count++;
        if (count == 10) {
            dispatch_suspend(timer);
            dispatch_semaphore_signal(semaphore);
        }
    });
    dispatch_resume(timer);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return count;
}

}


7、static变量支持

  MangoFix1.2.0版本中增加了对static变量进行了支持,MangoFix 的static变量和C语言中static变量特性基本一致。MangoFix中通过一张全局表对static变量进行管理,static变量只会初始化一致,static变量生命周期为从第一次初始化到App退出,static变量作用域和自动变量作用域一致,所以可以在不同作用域范围内,创建变量名相同的static变量也是不会冲突的。

8、取地址运算符

  MangoFix1.2.0版本中,增加了对取地址运算符&的支持,利用取地址运算符和static变量,MangoFix便能对GCD中的dispatch_once函数做很好的支持,比如下面的MangoFix示例代码:


class MFGetAddressOperatorTest : NSObject{

- (NSInteger)testGetAddressOperator{
    static int i = 0;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        i++;
    });
    return i;
}

}

9、如何在MangoFix中注入全局对象

  MangoFix中MFContext对象提供了 - (void)setObject:(MFValue *)value forKeyedSubscript:(NSObject <NSCopying> *)key;方法,便于用户向执行上下文中注入全局对象,比如在OC代码中执行下面代码:

context[@"globalVar"] = [MFValue valueInstanceWithBOOL:YES];
context[@"MyLog"] = [MFValue valueInstanceWithBlock:^void (id obj){
        NSLog(@"%@",obj);
    }];

分别表示向context注入全局的BOOL变量globalVar和名为MyLog的block。

10、MangoFix中如何针对不同App版本做不同的热修复处理

  MangoFix提供了条件注解#If(conditionExpr),可以在运行时做判断注解所作用的类、属性、方法是否使能,先看一下示例代码:

class MFConditionalReplaceTest : NSObject{

#If($systemVersion.doubleValue() >= 10.0 )
- (BOOL)testConditionalReplace{
    return NO;
}

}

上面代码表示只有当$systemVersion.doubleValue()值大于10.0才会对 - (BOOL)testConditionalReplace方法进行替换。 MangoFxi中已经内置了$systemVersion$appVersion$buildVersion等和版本相关的全局变量,分别表示:[UIDevice currentDevice].systemVersionCFBundleShortVersionStringCFBundleVersion,当然如果用户觉得不够还可以自己向MangoFix执行上下文中注入自定义的全局变量。

11、C函数变量

  早期MangoFix版本中已经将一些常用的C函数进行预埋,用户也可以自定义进行预埋,但是总有一些需要调用的C函数没有预埋到,所以MangoFix 1.3.0版本开始支持C函数变量,可以做到C函数声明即用无需预埋,C函数变量的定义和其他语言中的泛型很类似,格式如: CFunction<returnType,arg1Type,arg2Type,...> func, 尖括号中第一个type是函数返回值类型,其他的为函数形参类型,现在支持的类型有:voidBOOLintlongint8_tint16_tint32_tint64_tu_intu_longu_int8_tu_int16_tu_int32_tu_int64_tsize_tfloatdoubleCGFloatchar *void *idSELClassstruct structName,对于其他数据类型,要根据数据类型的大小选择上面一种数据类型,而C函数变量的值用CFunction("function_name")获取,对于dlsymdlopen这两个函数已被禁止动态调用,另外要注意的是CFunction("function_name")只支持获取动态链接的C函数。 下面我们看一段示例代码:

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<size_t, int, void *, size_t> write = CFunction("write");
CFunction<int, int> close = CFunction("close");



class  MFFuncDeclareTest : NSObject{

- (void)testFuncDeclare{
    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) {
            NSLog(@"创建文件失败");
            return;
        }
    }
    NSLog(path);
    int fd = open(path.UTF8String,O_WRONLY, S_IRWXU);
    if (fd < 0) {
        NSLog(@"打开文件失败");
        return;
    }
    NSURL *url = NSURL.URLWithString:(@"https://github.com/YPLiang19/Mango");
    NSData *data = NSData.dataWithContentsOfURL:(url);
    write(fd, data.bytes, data.length);
    close(fd);
}

}

12、类型别名

  MangoFix 1.3.0版本开始支持 typedef功能,格式为:typedef existingType newType; 比如:

typedef long alias_long;
alias_long var = 10;

13、MangoFix中自定义结构体的使用要注意什么

  MangoFix脚本中使用结构体,原则上是要先对结构体使用declare struct进行声明,但是MangoFix已经对常用的结构已经内置声明,已内置声明的结构如下:
CGPointCGSizeCGRectCGAffineTransformCGVectorNSRangeUIOffsetUIEdgeInsetsCATransform3D

在MangoFix中使用未声明的结构体,需要做如下声明:

declare struct MFCustomStruct {
    typeEncoding:"{MFCustomStruct=dd}",//@encode(struct MFCustomStruct)
    keys:x,y
}

特别需要注意的是:
1、在定义一个结构体变量时,需要在前面加入struct关键字:

  struct  UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

14、Masonry链式编程方式在MangoFix中如何编写

  有同学疑问对Masonry中的链式编程在MangoFix如何编写呢?其实这个写起来也是大同小异。需要注意的是,在MangoFix中对调用的方法如果是无参的,那么可以省去调用后面的一对括号,但是如果方法返回的是一个block对象,那么这对括号就不能省略,应为此时如果省略了方法调用括号,那么MangoFix解析器就无法知道,此时用户是想调用OC的对象方法,还是调用方法返回的block。下面是一个OC和MangoFix分别调用Masonry官方示例代码的对比:

    UIView *superview = self.view;
    UIView *view1 = [[UIView alloc] init];
    view1.translatesAutoresizingMaskIntoConstraints = NO;
    view1.backgroundColor = [UIColor greenColor];
    [superview addSubview:view1];
    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);
    }];
    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);
    });

上面部分是OC代码,下面部分是MangoFix代码,主要区别就是MangoFix代码在equalTooffset后面多了一对括号,就是避免MangoFix解析器产生歧义。再者就是MangoFix中UIEdgeInsets前的struct关键字不能省略。

15、MangoFix性能的如何

   根据本人测试,MangoFix的初始化速度是JSPatch的10倍左右,运行速度是JSPatch的2~5倍,内存占用方面并无太大区别。

16、MangoFix还有哪些不足

  • MangoFix不支持可变参数方法的调用和替换。
  • MangoFix调用C函数,需要预先通过注入全局对象方式,通过block将C函数预先埋入(1.3.0版本已支持C函数变量声明即用无需预埋)。
  • MangoFix不支持替换C函数。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • 《招聘一个靠谱的 iOS》—参考答案(下) 说明:面试题来源是微博@我就叫Sunny怎么了的这篇博文:《招聘一个靠...
    韩发发吖阅读 1,549评论 0 8
  • 敬爱的李老师,智慧的马教授,亲爱的跃友们: 大家好!我是来自山峰教外教育的张洪霞 今天是我的日精进行动第12...
    洪霞张阅读 189评论 0 1
  • 今天学习了第9章异常处理: 1.异常:程序在执行过程中发生的错误。 常见的异常: 文件找不到、文件操作权限不足、网...
    孙世龙阅读 175评论 0 0
  • 01 说起考研,我倒是有一些故事想说说。 我是在武汉读的大学,武汉的学习气氛,待过的同学应该都有所感应。甚至整个湖...
    徐小胖_阅读 679评论 0 3