OCRunner
介绍
使用 OCRunner 生成补丁的工作流程
所有参与方的责任
- oc2mangoLib 等同于一个简单的编译器,负责生成抽象语法树。
- ORPatchFile 负责序列化和解序列化抽象语法树,并确定版本是否可用。
- PatchGenerator 负责集成 oc2mangoLib 和 ORPatchFile 的功能。(所有上述工具都在 oc2mango 项目中)。
- OCRunner 负责执行抽象语法树。
与其他热修复库的区别
-
使用二进制补丁文件。提高安全性、减小补丁大小、优化启动时间,且可在PatchGenerator阶段优化。
-
自定义 Arm64 ABI (你还可以选择使用 libffi)
-
完全支持 Objective-C 语法,但不支持预编译和部分语法。
使用OCRunner本地运行补丁
OCRunnerDemo可以作为整个过程的参考。
Cocoapods
pod 'OCRunner' #Support all architectures, including libffi.a
# or
pod 'OCRunnerArm64' #Only supports arm64 and arm64e, does not include libffi.a
PatchGenerator
下载解压 PatchGenerato.zip,然后将 PatchGenerator 保存到 /usr/bin/ 或项目目录中。
运行脚本
为PatchGenerator添加-
项目设置 -> 构建阶段 -> 点击左上角的
+
->新建运行脚本阶段
-
为[PatchGenerator文件路径] -files [Objective-C源文件或目录] -refs [Objective-C头文件或目录] -output [保存补丁的路径]
-
例如:OCRunnerDemo中的
运行脚本
$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,调用
运行脚本
重新生成补丁文件。
在线环境
- 将补丁上传到资源服务器。
- 在应用中下载并保存补丁文件。
- 使用 [ORInterpreter excuteBinaryPatchFile:PatchFilePath] 执行补丁。
使用说明
结构、枚举、类型定义
您可以修改 OCRunnerDemo 中的 ViewController1 来运行以下代码。
// A new type called dispatch_once_t will be added
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();
提示
建议创建一个新文件来放置以上代码,类似于 OCRunnerDemo 中的 UIKitRefrence 和 GCDRefrence 文件,然后将补丁生成以 -links 的形式添加。
使用系统内置的 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");
您可以修改 OCRunnerDemo 中的 ViewController1 的内容来运行代码。
当你在脚本中添加此代码时,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];
修复Objective-C 的对象(类)方法并添加属性
若要修复方法,可以仅重写该方法,而不实现其他方法。
@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] // Add'ORG' before the method name to call the original method
[self otherMethod];
return self.strTypeProperty;
}
@end
使用块解决循环引用
__weak id object = [NSObject new];
// Minimal block
void (^a)(void) = ^{
int b = 0;
};
a();
使用 GCD
其本质是 使用系统内置的C函数。通过OCRunnerDemo中的 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();
使用内联函数、预编译函数
// Inline function: just add a global function in the patch, such as `CGRectMake` in UIKitRefrences
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;
}
// Pre-compiled function: you need to add the following code in the App
[[MFScopeChain top] setValue:[MFValue valueWithBlock:^void(dispatch_once_t *onceTokenPtr,
dispatch_block_t _Nullable handler){
dispatch_once(onceTokenPtr,handler);
}] withIndentifier:@"dispatch_once"];
如何确定源文件是否包含在补丁中
性能测试
加载时间
执行速度和内存使用
设备:iPhone SE2,iOS 14.2,Xcode 12.1。
以经典的斐波那契数列函数为例,查找第25项的测试结果
JSPatch
OCRunner
Mango
- 在测试递归函数时,OCRunner的性能是JSPatch的1/5,是Mango的2.5倍。
- OCRunner的补丁加载速度约为Mango的20倍以上,并且随着补丁大小的增加而增加,而JSPatch的值未知。
- 关于递归方法调用时的内存使用问题,目前存在过度使用的问题。在查找斐波那契数列的第30项时,Mango将崩溃,而OCRunner的峰值内存使用约为600MB。
当前问题
- 指针和乘号识别冲突,衍生问题:类型转换等。
- 不支持静态、内联函数声明。
- 不支持C数组声明:type a[]、type a[2]、value = { 0 , 0 , 0 , 0 }。
- 不支持'-'符号操作符...
- 不支持固定C函数。
支持的语法
- 类声明和实现,支持类别。
- 协议
- 块
- struct、enum、typedef
- 使用函数声明链接系统函数指针
- 全局函数
- 多参数调用(方法和函数)
- *、&(指针运算)
- 变量静态关键字
- NSArray: @[value1, value2],NSDictionary: @{ key: value }, NSNumer: @(value)
- NSArray、NSDictionary值和赋值语法:id value = a[var]; a[var] = value;
- 操作符,除'-'符号外,均已实现
等等。
感谢
- Mango
- libffi
- ARM 64位架构的调用约定。
- @jokerwking