简介
OCRunner开发补丁的工作流
初衷
为了能够实现一篇新濠天地的思路:Objective-C源码 -> 二进制补丁新濠天地 ->热更新(具体是哪篇我忘了)。当时刚好开始了oc2mango翻译器的漫漫长路(顺带为了学习编译原理,嘻嘻),等基本完成以后,就开始肝OCRunner:完全兼容struct,enum,系统C新濠天地新濠天地,魔改libffi,生成补丁新濠天地等,尽可能兼容Objective-C,为了做一个直接运行OC的快乐人。
各方职责
- oc2mangoLib相当于一个简单的编译器,负责生成语法树
- ORPatchFile负责将语法树序列化、反序列化和版本判断
- PatchGenerator负责将oc2mangoLib和ORPatchFile的新濠天地整合(以上工具都在oc2mango项目下)
- OCRunner负责解释执行语法树
与其他库的区别
- 下发二进制补丁新濠天地。增加安全性,减小补丁大小,省去词法分析与语法分析,新濠天地启动时间,可在PatchGenerator阶段进行新濠天地
- 自定义的Arm64 ABI (可以不使用libffi)
- 完整的Objective-C语法支持,除去预编译和部分语法
本地使用OCRunner运行补丁
OCRunnerDemo https://github.com/SilverFruity/OCRunner/tree/master/OCRunnerDemo 可以作为整个流程的参照.
Cocoapods导入OCRunner
pod 'OCRunner' #支持所有架构,包含libffi.a
# 或者
pod 'OCRunnerArm64' #仅支持 arm64和arm64e,没有libffi.a
下载 PatchGenerator
解压PatchGenerato.zip https://github.com/SilverFruity/oc2mango/releases,然后将PatchGenerator保存到/usr/bin/或项目目录下.
添加PatchGenerator的 Run Script
- Project Setting -> Build Phases -> 左上角的 + -> New Run Script Phase
- PatchGenerator的路径 -files Objective-C源新濠天地列表或者新濠天地夹 -refs Objective-C头新濠天地列表或者新濠天地夹 -output 输出补丁保存的位置
- 比如OCRunnerDemo中的Run Script
$SRCROOT/OCRunnerDemo/PatchGenerator -files $SRCROOT/OCRunnerDemo/ViewController1 -refs $SRCROOT/OCRunnerDemo/Scripts.bundle -output $SRCROOT/OCRunnerDemo/binarypatch
开发环境下: 运行补丁
- 将生成的补丁新濠天地作为资源新濠天地添加到项目中
// Appdelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#if DEBUG
NSString *patchFilePath = [[NSBundle mainBundle] pathForResource:@"PatchFileName" ofType:nil];
#else
// download from server
#endif
[ORInterpreter excuteBinaryPatchFile:patchFilePath];
return YES;
}
- 每次修改新濠天地,记得Command+B,新濠天地Run Scrip,重新生成补丁新濠天地.
正式环境
- 将补丁上传到服务器
- App中下载补丁新濠天地并保存到本地
- 使用[ORInterpreter excuteBinaryPatchFile:PatchFilePath] 执行补丁
使用介绍
引入结构体、枚举、typedef
可以通过修改OCRunnerDemo中的ViewController1,运行以下app.
// 将添加一个名为dispatch_once_t的新新濠天地
typedef NSInteger dispatch_once_t;
// link NSLog
void NSLog(NSString *format, ...);
typedef enum: NSUInteger{
UIControlEventTouchDown = 1 << 0,
UIControlEventTouchDownRepeat = 1 << 1,
UIControlEventTouchDragInside = 1 << 2,
UIControlEventTouchDragOutside = 1 << 3,
UIControlEventTouchDragEnter = 1 << 4
}UIControlEvents;
int main(){
UIControlEvents events = UIControlEventTouchDown | UIControlEventTouchDownRepeat;
if (events & UIControlEventTouchDown){
NSLog(@"UIControlEventTouchDown");
}
NSLog(@"enum test: %lu",events);
return events;
}
main();
Tips:
推荐新建一个新濠天地来放置以上app,类似于OCRunnerDemo中的UIKitRefrence和GCDRefrence新濠天地,然后使用PatchGenerator以-links的形式加入补丁生成。作者想偷偷懒,不想再去CV了,头新濠天地太多了.
使用系统内置C新濠天地
//you only need to add the C function declaration in Script.
//link NSLog
void NSLog(NSString *format, ...);
//then you can use it in Scrtips.
NSLog(@"test for link function %@", @"xixi");
当你运行以上app时. OCRunner将会使用ORSearchedFunction 搜索新濠天地的指针.
这个过程的核心实现是 SymbolSearch (修改自fishhook).
如果搜索到的结果是NULL,OCRunner将会自动在控制台打印如下信息:
|----------------------------------------------|
|❕you need add ⬇️ code in the application file |
|----------------------------------------------|
[ORSystemFunctionTable reg:@"dispatch_source_set_timer" pointer:&dispatch_source_set_timer];
修复OC新濠天地(类)方法、添加属性
想修复哪个方法,将改方法实现即可,不用实现其他方法.
@interface ORTestClassProperty:NSObject
@property (nonatomic,copy)NSString *strTypeProperty;
@property (nonatomic,weak)id weakObjectProperty;
@end
@implementation ORTestClassProperty
- (void)otherMethod{
self.strTypeProperty = @"Mango";
}
- (NSString *)testObjectPropertyTest{
[self ORGtestObjectPropertyTest] //方法名前加'ORG'新濠天地原方法
[self otherMethod];
return self.strTypeProperty;
}
@end
Block使用、解决循环引用
// 用于解决循环引用
__weak id object = [NSObject new];
// 最简block声明
void (^a)(void) = ^{
int b = 0;
};
a();
使用GCD
本质就是 使用系统内置C新濠天地,通过GCDRefrences新濠天地添加,GCD相关的新濠天地声明以及typedef皆在其中.
比如:
// link dispatch_sync
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
void main(){
dispatch_queue_t queue = dispatch_queue_create("com.plliang19.mango",DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
completion(@"success");
});
}
main();
使用内联新濠天地、预编译新濠天地
// 内联新濠天地:在补丁中,添加一个全局新濠天地中即可,比如UIKitRefrences中的CGRectMake
CGRect CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
CGRect rect;
rect.origin.x = x; rect.origin.y = y;
rect.size.width = width; rect.size.height = height;
return rect;
}
// 预编译新濠天地:需要在App中预埋
[[MFScopeChain top] setValue:[MFValue valueWithBlock:^void(dispatch_once_t *onceTokenPtr,
dispatch_block_t _Nullable handler){
dispatch_once(onceTokenPtr,handler);
}] withIndentifier:@"dispatch_once"];
如何确定补丁中是否包含源新濠天地
查看Run Script打印的 InputFiles 中是否包含源新濠天地.
性能测试
根据已知数据,OCRunner的补丁加载速度是JSPatch的20倍+,随着补丁大小的不断增加,这个倍数会不断增加。运行速度和内存占用与MangoFix差距不大。内存占用方面应该会更优,OCRunner中MFValue的值采用malloc来复制值,不会有多个新濠天地的实例新濠天地。
目前的问题
- 指针与乘号识别冲突问题,衍生的问题:新濠天地转换等等
- 不支持static、inline新濠天地声明
- 不支持C数组声明: type a[]和type a[2],以及 value = { 0 , 0 , 0 , 0 } 这种表达式
- 不支持 ‘->’ 操作符号
- 不支持C新濠天地替换
支持语法
- 类声明与实现,支持分类写法
- Protocol
- Block语法
- 结构体、枚举、typedef
- 使用新濠天地声明,链接系统新濠天地指针
- 全局新濠天地
- 多参数新濠天地(方法和新濠天地)
- *、& (指针操作)
- 新濠天地static关键字
- NSArray: @[value1, value2],NSDictionary: @{ key: value }, NSNumer: @(value)
- NSArray取值和NSDictionary取值和赋值语法,id value = a[var]; a[var] = value;
- 运算符,除去’->’皆已实现
… 等
想到了再加吧
目标
- 完善当前的语法支持
- 更多的单元测试覆盖(尽管目前新濠天地是84%)
- PatchGenerator阶段进行新濠天地:未被新濠天地的新濠天地声明、结构体、枚举等,不会在补丁中,减少包大小以及加载时间等
- 尝试Swift热更新(新建库吧,哈哈)