OCRunner QQ群: 860147790
简介
使用 OCRunner 生成补丁的工作流程
各方的职责
- oc2mangoLib 相当于一个简单的编译器,负责生成抽象语法树。
- ORPatchFile 负责序列化和反序列化抽象语法树,并确定版本是否可用。
- PatchGenerator 负责整合 oc2mangoLib 和 ORPatchFile 的功能。(所有上述工具都在 oc2mango 项目中)。
- OCRunner 负责执行抽象语法树。
与其他热补丁库的区别
-
使用二进制补丁文件。提高安全性,减小补丁大小,优化启动时间,可以在PatchGenerator阶段优化。
-
自定义Arm64 ABI(也可选择使用libffi)
-
完全支持Objective-C语法,但不支持预编译和部分语法。
使用OCRunnerDemo在本地运行补丁
OCRunnerDemo可以作为整个过程的参考。
无法通过下载ZIP文件成功运行。您必须使用以下shell命令来运行OCRunnerDemo。
git clone --recursive https://github.com/SilverFruity/OCRunner.git
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/local/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,调用
执行脚本
来重新生成补丁文件。
在线环境
- 将补丁上传到资源服务器。
- 在 App 中下载并保存补丁文件。
- 使用 [ORInterpreter excuteBinaryPatchFile:PatchFilePath] 执行补丁。
使用说明
结构,枚举,类型定义
您可以通过修改 ViewController1 中的代码在 OCRunnerDemo 中运行以下代码。
// 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
使用 Block 并解决循环引用
__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