最近在ios群里面看到某应用因为hotpatch审核被拒绝, 如果hotpatch全面被封禁, 那还不如全切swift, 又能提高性能, 又能减少编码中犯的错误. 仔细想想如果swift也有办法被hotpatch, 不就更加完美了?
hotpatch是无法被全面封禁的, 可爱的程序猿们总能有应对的办法
swift有四种方法调用方式:
inline method
static dispatch
dynamic dispatch
message send
<code>inline method</code>会在编译期间将被调用的方法直接内联到调用方法的方法体里面, 这种状况下方法调用将没有任何开销.
示例代码如下:
编译后的汇编代码是:
可以看出汇编代码超级简洁, 就只剩两次对clock方法的调用.
<code>static dispatch</code>会通过汇编指令<code>bl</code>跳转到被调用方法所在的地址, 地址在编译期就已经决定. 但因为多了一次<code>bl</code>指令, 会有少量的时间消耗.
对代码做不inline处理:
这里比面上多出来一条<code>bl</code>指令, 跳转到hehe1方法所在地址.
<code>dynamic dispatch</code>会生成一张类的方法表(在数据段), 在调用时通过<code>ldr</code>指令从类表取出方法所在的地址到寄存器, 再跳转到方法所在的地址. 这种方式需要3条指令及1次内存访问, 所耗费的时间更长.
代码同第一段代码, 把<code>工程build settings的swift comipler - code generation的optimization level调为none</code>, 反编译代码如下:
注: 此段代码汇编代码经过精简, 代码的解释见注释
<code>message send</code>就是objc_msgsend的流程, 这里暂不多做介绍.
inline的代码明显没戏, 连独立的方法都没有了, 更没有方法调用.
static的代码也没戏, 调用的方法地址在编译期就决定好了, 无法动态的做改变(代码在__text段, 代码加载到内存后无写权限, 无法更改).
message用oc那一套方案就可以了.
dynamic的代码中, 是通过加载class的元数据中存储的方法地址进行方法调用, 而class元数据位于__data段, __data段是可以读写的. 那不就意味着采用dynamic方式, 把class的元数据给篡改掉就可以patch了? 我们试试!
先来一段原始代码:
按照正常的执行流程, 我们会在调试窗口看到:
我想把hehe1这个方法patch到另一个方法的实现(一个c方法):
从之前提到的dynamic调用方式的修改class元数据的思路展开说, 我们首先要获取到aclass的class元数据的地址, 再获取到hehe1方法指针在元数据中的偏移量, 再获取patched_hehe1方法的指针, 再塞到class元数据中hehe1方法对应的位置.
开干!
下面实现了patch_hehe1方法, 将hehe1方法给patch成patched_hehe1方法:
调用patch_hehe1方法后, aclass的hehe1方法就被patch掉了! 运行程序看调试窗口的结果:
patch成功!
在前面提到, 为了实现让swift走dynamic dispatch, 将编译选项中的优化级别设为了none. 那如果将优化级别恢复为<code>fast</code>后情况会变成什么样呢?

<code>hehe1方法被inline, patch无效, 输出hehe1</code>
那么问题来了! 你愿意牺牲性能换取动态性么?