以下笔记整理于2017-03-22
当时作为无埋点预研的学习整理,由于小组是客户端和前端混合的小组,所以下面尽可能以偏简单的文字在组内分享。
- 用最简单的文字,来简单说明Method Swizzling和JSPatch hook selector的原理。并试验当两者共用时,兼容情况。(无埋点预研)
- 如果看此文之前,对这两者有些概念那就最好啦😝
1 | 目录: |
1、进入正文之前,从[receiver message]说起
OC 动态语言
执行函数的过程就是发送消息的过程,根据message生成selector。简化版流程图如下:
说明:
- 发送消息,根据
message
生成selector - 根据selector寻找指向函数实现的指针IMP,存在直接执行
不存在进入消息转发机制 resolveInstanceMethod
:动态方法解析-常用于给类/对象动态添加一个相应的实现forwardingTargetForSelector
:备用接收者-将消息转发给其他对象处理forwardInvocation
:完整转发-最后一次将消息转发给其他对象,包含一个表示消息的NSInvocation
的对象,包含尚未处理的消息的全部细节,包括selector
、target
、params
- 以上都没完成转发,则
doesNotRecognizeSelector
,此时就会看到我们常见的unrecognize sent to selector balabala
之类的错误
2、 Method Swizzling
以method_exchangeImplementations
为例
- hook关键:未进入消息转发机制时,直接从IMP hook,交换2个方法的IMP
以selORIG
为原方法,IMPORIG
为对应指向函数实现的指针
原始方法
hook selORIG 一次
hook selORIG 两次,先A后B
第二次hook,实质上已经跟selA
无关,是selORIG
和selB
对应的IMP
的交换,因此selB
对应IMPA
,selORIG
对应IMPB
。而selA
不受影响仍然是IMPORIG
- hook selORIG 三次,顺序为:A、B、C
经过两次hook的解释,应该懂它的套路了
以上:不同的hook,是在不同的地方实现
先后顺序的保证:由于Method Swizzling
都是写在+(void)load
方法中的,compile Sources
的顺序就是load方法的调用顺序(注意这里是已分类的形式的情况下,如果存在父类和子类同时swizzle,是先执行父类的+load
,这也是一个坑-后续再讨论)
关于Method Swizzling
如何实现,自行Google,😝
3、JSPatch
相当普及的热修复库,虽然目前iOSer都收到苹果爸爸的警告邮件了,这个框架的未来走势还有待观察,但是智慧无穷,它总会存在。
一点说明
因为本文最后要讲Method Swizzling
和JSPatch
的兼容性。因此下文,不会涉及JS是如何和OC交互的?JSPatch很细节的实现是什么?只会涉及,它是如何hook,在哪一步hook
在哪一步&如何hook
白话文概括其原理:
1、 改变原指向函数实现的IMP指针为_objc_msgForward
(或_objc_msgForwad_stret
)
2、 执行方法,直接进入消息转发过程
3、 消息转发最后一步hook-forwardInvocation
下面用图来说明:
- 搞事前:安然无恙
- 搞事第一步:改变方法指向
_objc_msgForward
如上图:用过JSPatch都知道,我们可以通过self.ORIGtest()
的方式调用原方法,是因为JSPatch新增了一个方法指向原来的方法
1 | //提取几行关键的源码 |
你可能会有疑问的地方,Answer见第7部分描述
Q:万一,原方法不存在怎么办?
Q:`_objc_msgForward`是什么鬼?
- 搞事第二步:
forwardInvocation
1 |
|
- 搞事第三步:
JPForwardInvocation
里到底做了啥?
经过搞事第一步我们知道,我们现在调用方法会直接进入消息转发机制的最后一步(以下是:包含关键步骤的不完整流程)。
一句话:获得NSInvocation所有参数,将消息转发给JS对应实现,获得JS回调结果,完成调用
- Step01:判断调用的方法该类是否已实现,未实现则走 原方法指向的消息转发流程(上图中即
IMPforwardInvocation
)
1 | JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName); |
- Step02:处理调用函数的参数等相关信息
这里invocation会被利用,关于invocation,前文已经提及
这里涉及OC的type encoding(类型编码)
Step03:通过
formatOCToJS
将参数数组转为对应的JS对象数组Step04:调用JS实现,传入之前处理的参数列表
主要涉及的方法:- (JSValue *)callWithArguments:(NSArray *)arguments;
你可能会有疑问的地方,Answer见第7部分描述
1 | Q:关于type encoding |