OCRunner:完全体的iOS热修复方案

简介

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,运行以下代码.

// 将添加一个名为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:

推荐新建一个文件来放置以上代码,类似于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");

当你运行以上代码时. 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来复制值,不会有多个类型的实例变量。

目前的问题

  1. 指针与乘号识别冲突问题,衍生的问题:类型转换等等
  2. 不支持static、inline函数声明
  3. 不支持C数组声明: type a[]和type a[2],以及 value = { 0 , 0 , 0 , 0 } 这种表达式
  4. 不支持 ‘->’ 操作符号
  5. 不支持C函数替换

支持语法

  1. 类声明与实现,支持分类写法
  2. Protocol
  3. Block语法
  4. 结构体、枚举、typedef
  5. 使用函数声明,链接系统函数指针
  6. 全局函数
  7. 多参数调用(方法和函数)
  8. *、& (指针操作)
  9. 变量static关键字
  10. NSArray: @[value1, value2],NSDictionary: @{ key: value }, NSNumer: @(value)
  11. NSArray取值和NSDictionary取值和赋值语法,id value = a[var]; a[var] = value;
  12. 运算符,除去’->’皆已实现

… 等

想到了再加吧

目标

  • 完善当前的语法支持
  • 更多的单元测试覆盖(尽管目前显示是84%)
  • PatchGenerator阶段进行优化:未被调用的函数声明、结构体、枚举等,不会在补丁中,减少包大小以及加载时间等
  • 尝试Swift热更新(新建库吧,哈哈)

https://mp.weixin.qq.com/s/cHepcupQdxnVpWIIw82tXw

iOS 性能优化:优化 App 启动速度

苹果是一家特别注重用户体验的公司,过去几年一直在优化 App 的启动时间,特别是去年的 WWDC 2019 keynote[1] 上提到,在过去一年苹果开发团队对启动时间提升了 200%

发布于:2天以前  |  23次阅读  |  详细内容 »

让你的应用远离越狱:iOS 14 App Attest 防护功能

当越狱在 iOS 设备第一次流行起来时,iOS 开发人员会尝试各种方法来保护自己的应用程序,以让应用免受盗版等不确定因素的困扰。有许多方法可以做到这一点,包括检查 Cydia 是否存在、检测应用程序是否可读取自身沙箱之外的文件、在检测到调试器时让应用程序崩溃等等。

发布于:9天以前  |  53次阅读  |  详细内容 »

探秘 iOS 14 的 WidgetKit

Widget Extension 提供了 small, medium, large 三个尺寸,不同尺寸可以展示不同的数据、不同的界面,开发者也可以锁定自己APP的 Widget 只有某类尺寸,相同的widget也能重复添加。作为添加在主屏幕上的控件,苹果用了 “At a glance” 来形容 widget ,所以 widget extension 是无法交互的,它能做的只有展示一些信息与点击两个作用,点击后就会引导至app,同时为了性能与耗电量的考虑,Widget extension 也不能展示视频和动态图像。

发布于:11天以前  |  76次阅读  |  详细内容 »

iOS14 Widget 万字指北,先人一步获得顶级流量

2020 年 6 月 22 日,苹果召开了第一次线上的开发者大会 - WWDC20。这次发布会上宣布了ARM架构Mac芯片(拳打Intel)、iOS 14 ATT(脚踢Facebook),可谓是一次载入史册(我是爸爸)的发布会了,当然还发布了被称为下一个顶级流量入口的Widget。踩着八月的尾巴,本次我们就来探究一下Widget。本文会从Widget初窥和Widget开发两个维度和章节来探究一下Widget, 其中初窥章节会带您简单的了解一下Widget,适合应用决策者阅读; 开发章节会带着您一步一步的完成设计开发Widget,适合程序员阅读。

发布于:11天以前  |  80次阅读  |  详细内容 »

OCRunner:完全体的iOS热修复方案

为了能够实现一篇文章的思路:Objective-C源码 -> 二进制补丁文件 ->热更新(具体是哪篇我忘了)。当时刚好开始了oc2mango翻译器的漫漫长路(顺带为了学习编译原理,嘻嘻),等基本完成以后,就开始肝OCRunner:完全兼容struct,enum,系统C函数调用,魔改libffi,生成补丁文件等,尽可能兼容Objective-C,为了做一个直接运行OC的快乐人。

发布于:11天以前  |  75次阅读  |  详细内容 »

iOS APP图标版本化

在我们的项目开发过程中,需要频繁打包给测试人员去测试,有时候我们都不知道测试机上安装的版本是否是最新的,这样会造成很多不必要的麻烦和成本。因此我们需要将buildNumber以水印的方式打在APPIcon上,可以很直观的知道当前是哪一个版本。

发布于:11天以前  |  71次阅读  |  详细内容 »

百度App iOS工程化实践: EasyBox破冰之旅

百度App从单一的搜索工具发展到今天以搜索和Feed流为双引擎的综合性内容消费服务平台,其复杂程度已然不可同日而语矣。 作为一个日活过亿的超级App,业务规模庞大,相关技术人员超过千人,客户端支持主流的移动技术,涉及近百业务方,技术形态复杂,各种组件近三百个,代码百万量级,由此带来的工程化问题是技术团队的一个极大挑战。

发布于:11天以前  |  84次阅读  |  详细内容 »

百度APP iOS暗黑模式适配的完美解决方案

在2019WWDC的开场演讲中,苹果公布了即将推出的iOS13 DarkMode的新特性。此新特性不仅可以在夜晚保护视力,而且对于使用OLED的最新一代设备而言,也可以帮助用户节省电量消耗。不过此特性只支持iOS13以上的系统,为了给全系统所有用户最好的体验,研发出了一套皮肤主题框架,不仅可以全系统支持DarkMode,还可以扩展多套皮肤主题;

发布于:11天以前  |  77次阅读  |  详细内容 »

iOS 14 - 使用 PHPicker 选择照片和视频

绝大多数的 App 都要和相册打交道,选择照片或者视频,要么用来发个朋友圈,要么是放到什么地方做个背景。从 AssertLibrary 到 Photos 框架,苹果已经在多年之前就给相册相关的 API 做过一次大升级了。

发布于:13天以前  |  98次阅读  |  详细内容 »

iOS开发体验优化方案

随着Flutter等跨端框架的出现,业务开发同学经常需要在Android/iOS上跨端进行业务开发,问题定位等。新的不熟悉的环境的搭建总会遇到各种各样的问题,导致搭建失败,特别是iOS开发环境,是最复杂的,不仅环境搭建繁琐,而且切分支后的打包速度很慢,所以我们设计实现了两个工具,用于优化闲鱼iOS开发体验。

发布于:26天以前  |  127次阅读  |  详细内容 »

iOS14:再见了,“流氓”APP!

最近和苹果有关的重大消息可能就是从8月1日开始,AppStore中国区火速下架未获版号的游戏APP,数量超过30000款,之前小智就和大家说过,这未必不是一件好事,众多低质和“流氓”APP将被最大限度隔绝在iOS系统之外。

发布于:28天以前  |  177次阅读  |  详细内容 »

iOS 14 苹果对 Objective-C Runtime 的优化

Objective-C 是一门古老的语言,诞生于 1984 年,跟随 Apple 一路浮沉,见证了乔布斯创建了 NeXT,也见证了乔布斯重回 Apple 重创辉煌,它用它特立独行的语法,堆砌了 UIKit,AppKit, Foundation 等一个个基石,时间来到 2020 年,面对汹涌的"后浪" Swift,"老前辈" Objective-C 也在发挥着自己的余热,即使面对越来越多阵地失守,唯有“老兵不死,只会慢慢凋亡"才能体现的悲壮。今年,Apple 给 Objective-C Runtime 带来了新的优化,接下来,让我们深入理解这些变化。

发布于:1月以前  |  275次阅读  |  详细内容 »

iOS14 隐私适配及部分解决方案

在刚刚结束的线上 WWDC 2020 发布会上苹果向我们展示了新的 iOS14 系统。iOS14 的适配,很重要的一环就集中在用户隐私和安全方面。 在 iOS13 及以前,当用户首次访问应用程序时,会被要求开放大量权限,比如相册、定位、联系人,实际上该应用可能仅仅需要一个选择图片功能,却被要求开放整个照片库的权限,这确实是不合理的。对于相册,在 iOS14 中引入了 “LimitedPhotos Library” 的概念,用户可以授予应用访问其一部分的照片,对于应用来说,仅能读取到用户选择让应用来读取的照片,让我们看到了 Apple 对于用户隐私的尊重。这仅仅是一部分,在iOS14 中,可以看到诸多类似的保护用户隐私的措施,也需要我们升级适配。 最近在调研 iOS14的适配方案,本文主要分享一下 iOS14 上对于隐私授权的变更和部分适配方案,欢迎补充指正。

发布于:2月以前  |  324次阅读  |  详细内容 »

Metal新特性:大幅度提升iOS端性能

Metal 是一个和 OpenGL ES 类似的面向底层的图形编程接口,通过使用相关的 api 可以直接操作 GPU ,最早在 2014 年的 WWDC 的时候发布。Metal 是 iOS 平台独有的,意味着它不能像 OpenGL ES 那样支持跨平台,但是它能最大的挖掘苹果移动设备的 GPU 能力,进行复杂的运算,像 Unity 等游戏引擎都通过 Metal 对 3D 能力进行了优化, App Store 还有相应的运用 Metal 技术的游戏专题。 闲鱼团队是比较早在客户端侧选择Flutter方案的技术团队,当前的闲鱼工程里也是一个较为复杂的Native-Flutter混合工程。作为一个2C的应用,性能和用户体验一直是闲鱼技术团队在开发中比较关注的点。而Metal这样的直接操作GPU的底层接口无疑会给闲鱼技术团队突破性能瓶颈提供一些新的思路。 下面会详细阐述一下这次大会Metal相关的新特性,以及对于闲鱼技术和整个淘系技术来说,这些新特性带来了哪些技术启发与思考。

发布于:2月以前  |  285次阅读  |  详细内容 »

最多阅读

快速配置 Sign In with Apple 1年以前  |  3180次阅读
给数组NSMutableArray排序 1年以前  |  2325次阅读
开篇 关于iOS越狱开发 1年以前  |  2322次阅读
APP适配iOS11 1年以前  |  2245次阅读
在越狱的iPhone设置上使用lldb调试 1年以前  |  2228次阅读
UITableViewCell高亮效果实现 1年以前  |  2159次阅读
App Store 审核指南[2017年最新版本] 1年以前  |  2075次阅读
使用 GPUImage 实现一个简单相机 1年以前  |  2042次阅读
所有iPhone设备尺寸汇总 1年以前  |  2011次阅读
关于Xcode不能打印崩溃日志 1年以前  |  1930次阅读
使用ssh访问越狱iPhone的两种方式 1年以前  |  1926次阅读
使用ssh 访问越狱iPhone的两种方式 1年以前  |  1795次阅读
UIDevice的简单使用 1年以前  |  1658次阅读
为对象添加一个释放时触发的block 1年以前  |  1620次阅读
使用最高权限操作iPhone手机 1年以前  |  1561次阅读