今天向你们介绍的是iOS热修复的另外一解决方案:MangoFix。介绍他的缘由是他和传统的iOS热修复使用JavaScript bridge 的方式彻底不一样,MangoFix是一个语法和OC语法很是相似的DSL,其语言自己的设计目标就是为了解决iOS热修复问题,因此在使用的便捷程度和性能方面都要远远超过传统的iOS 热修复SDK,好比JSPatch。下面从如下几点介绍MangoFix,更具体的请参考GitHub文档和MangoFix单元测试。html
1 首先经过CocoaPods安装MangoFix :pod 'MangoFix'
git
2 引入MangoFix头文件:#import <MangoFix/MangoFix.h>
github
3 建立MangoFix脚本执行上下文对象MFContext
实例编程
4 运行MangoFix脚本文件bash
示例代码以下:app
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
关键字。下面示例:async
class MFInstanceMethodReplaceTest : NSObject {
- (BOOL)testInstanceMethodReplace{
return YES;
}
}
复制代码
对于类方法的替换只需将方法返回值类型前的-修改成+便可。
须要注意的是:
一、继承的父类不能够省略。函数
MangoFix中为对象添加属性和OC同样,支持的修饰符有: weak
、 strong
、 copy
、 assign
、 nonatomic
、atomic
。下面看一下示例代码:性能
class MFObjectPropertyTest : NSObject{
@property(nonatomic, copy)NSString *propertyName;
- (NSString *)testObjectPropertyTest{
return self.strTypeProperty;
}
}
复制代码
须要注意的是:
一、属性不支持class修饰符。
二、MangoFix是经过objc_setAssociatedObject
实现属性值的存储,可是MangoFix在解析时候作了处理,访问属性值也能够经过_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;
}
}
复制代码
须要注意的是:
一、在无参block定义时,不能够加void
声明。
二、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
只能放在变量类型以前。
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;
}
}
复制代码
MangoFix1.2.0版本中增长了对static变量进行了支持,MangoFix 的static
变量和C语言中static
变量特性基本一致。MangoFix中经过一张全局表对static
变量进行管理,static
变量只会初始化一致,static
变量生命周期为从第一次初始化到App退出,static
变量做用域和自动变量做用域一致,因此能够在不一样做用域范围内,建立变量名相同的static
变量也是不会冲突的。
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;
}
}
复制代码
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版本中已经将一些经常使用的C函数进行预埋,用户也能够自定义进行预埋,可是总有一些须要调用的C函数没有预埋到,因此MangoFix 1.3.0版本开始支持C函数变量,能够作到C函数声明即用无需预埋,C函数变量的定义和其余语言中的泛型很相似,格式如: CFunction<returnType,arg1Type,arg2Type,...> func
, 尖括号中第一个type是函数返回值类型,其余的为函数形参类型,如今支持的类型有:void
、BOOL
、int
、long
、int8_t
、int16_t
、int32_t
、int64_t
、u_int
、u_long
、u_int8_t```、
u_int16_t、
u_int32_t、
u_int64_t、
size_t、
float、
double、
CGFloat、
char *、
void *、
id、
SEL、
Class、
struct structName,对于其余数据类型,要根据数据类型的大小选择上面一种数据类型,而C函数变量的值用
CFunction("function_name")获取,对于
dlsym和
dlopen这两个函数已被禁止动态调用,另外要注意的是
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);
}
}
复制代码
MangoFix 1.3.0版本开始支持 typedef
功能,格式为:typedef existingType newType
; 好比:
typedef long alias_long;
alias_long var = 10;
复制代码
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
}
复制代码
特别须要注意的是:
一、在定义一个结构体变量时,须要在前面加入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倍,内存占用方面并没有太大区别。
转载于做者:知水为命
连接:www.jianshu.com/p/7ae91a2da…
给你们推荐一个优秀的iOS交流群,群里的伙伴们都是很是优秀的iOS开发人员,咱们专一于技术的分享与技巧的交流,你们能够在群讨论技术,交流学习。欢迎你们的加入761407670(密码123)。