00001154
今天向大家介绍的是iOS热修复的另一解决方案:MangoFix。介绍他的原因是他和传统的iOS热修复使用JavaScript bridge 的方式完全不同,MangoFix是一个语法和OC语法非常类似的DSL,其语言本身的设计目标就是为了解决iOS热修复问题,所以在使用的便捷程度和性能方面都要远远超过传统的iOS 热修复SDK,比如JSPatch。下面从以下几点介绍MangoFix,更具体的请参考GitHub文档和MangoFix单元测试。
NSString *path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"mg"]; NSURL *scriptUrl = [NSURL fileURLWithPath:path]; MFContext *context = [[MFContext alloc] init]; [context evalMangoScriptWithURL:scriptUrl];
MangoFix可以替换或创建任意OC对象实例方法或类方法,语法和OC类似,不过在类的定义上采用class关键字。下面示例:
class MFInstanceMethodReplaceTest : NSObject { - (BOOL)testInstanceMethodReplace{ return YES; } }
对于类方法的替换只需将方法返回值类型前的-修改为+即可。
需要注意的是:
1、继承的父类不可以省略。
MangoFix中为对象添加属性和OC一样,支持的修饰符有:weak、strong、copy、assign、nonatomic、atomic。下面看一下示例代码:
class MFObjectPropertyTest : NSObject{ @property(nonatomic, copy)NSString *propertyName; - (NSString *)testObjectPropertyTest{ return self.strTypeProperty; } }
需要注意的是:
1、属性不支持class修饰符。
2、MangoFix是通过objc_setAssociatedObject实现属性值的存储,所以不可以通过_ propertyName方式访问属性值。
在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关键字后面不需要加*运算符。
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"); }); } }
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。
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].systemVersion、CFBundleShortVersionString、CFBundleVersion,当然如果用户觉得不够还可以自己向MangoFix执行上下文中注入自定义的全局变量。
MangoFix脚本中使用结构体,原则上是要先对结构体使用declare struct进行声明,但是MangoFix已经对常用的结构已经内置声明,已内置声明的结构如下:
CGPoint、CGSize、CGRect、CGAffineTransform、CGVector、NSRange、UIOffset、UIEdgeInsets、CATransform3D
在MangoFix中使用未声明的结构体,需要做如下声明:
declare struct MFCustomStruct { typeEncoding:"{MFCustomStruct=dd}",//@encode(struct MFCustomStruct) keys:x,y }
特别需要注意的是:
1、在定义一个结构体变量时,需要在前面加入struct关键字:
struct UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
有同学疑问对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代码在equalTo和offset后面多了一对括号,就是避免MangoFix解析器产生歧义。再者就是MangoFix中UIEdgeInsets前的struct关键字不能省略。
根据本人测试,MangoFix的初始化速度是JSPatch的10倍左右,运行速度是JSPatch的2~5倍,内存占用方面并无太大区别。