libffi 探究
前言
自苹果禁用热更新以来(实际上就是禁用了 dlsym
等几个接口),使用了 JSpatch 等热更新库的应用也就无法更新了;
那么有没有一种方式可以代替通过 dlsym
实现的热更新呢?
OCRunner & MangoFix
这两个库都可以实现 iOS 的热更新,使用的原理是相同的,都是通过语法分析、词法分析最终生成抽象语法树,再通过解析器解析,这里相当于自己写了一个编译器;而底层方法交换是通过 libffi + runtime 实现的,这篇文章就来简单了解下 libffi 这个库的使用。
libffi
FFI
的全名是 Foreign Function Interface (外部函数接口)
libffi 提供了一套底层接口,在知道函数签名的情况下,可以根据相关接口完成函数调用;
调用惯例(Calling Convention)
函数调用是通过堆栈体现出来的,在调用函数时,需要按照约定将相关的参数入栈,
而这种约定就叫做:调用惯例(Calling Convention)
也就是说只要我们按照这个约定存放函数调用时使用的参数,就可实现函数调用的效果;
libffi 也就是实现了这样的一个功能。
libffi 调用任意 OC 方法
实现步骤:
- 通过 libffi 创建 closure 闭包
- 交换函数指针;之后调用原始方法,因为 imp 已经修改,最终会调用到闭包中
- 在闭包回调函数里面,将 imp 替换成新的,将消息通过 ffi_call 发送出去
换句话说通过 libffi 的闭包功能,再加上 OC 提供给我们的 runtime ,一样也可以实现任意方法的 hook 功能;同时也为热修复提供了基础能力。
创建闭包并交换 IMP
1 | - (void)closureInit { |
- ffi_type 表示参数类型
- ffi_prep_cif 负责初始化函数模板(相当于函数签名)
- ffi_closure_alloc 分配空间
- ffi_prep_closure_loc 绑定闭包数据
将闭包回调转发到新的IMP上
1 | void ffiClosureCalled(ffi_cif *cif, void *ret, void **args, void *userdata) { |
缓存
ffi 生成的闭包数据必须缓存起来,这里写了个类单独处理闭包相关逻辑。
考虑到每个类可以 hook 多个方法,每个方法又必须对应一个闭包,所以缓存结构就是一个哈希表,key 表示 class,value 表示多个方法的集合,集合也是一个哈希表,key表示方法名,value表示对应的闭包;
遗留问题
- 闭包释放时要怎么销毁内存