Method Swizzling与JSPatch探究(一)
以下笔记整理于2017-03-22
当时作为无埋点预研的学习整理,由于小组是客户端和前端混合的小组,所以下面尽可能以偏简单的文字在组内分享。
第二部分地址为:Method Swizzling与JSPatch探究(二)
- 用最简单的文字,来简单说明Method Swizzling和JSPatch hook selector的原理。并试验当两者共用时,兼容情况。(无埋点预研)
-
如果看此文之前,对这两者有些概念那就最好啦
目录: 1、从[receiver message]说起 2、Method Swizzling hook过程 3、JSPatch hook过程 4、各种case兼容性 5、结论 6、拓展 7、也许你有疑问的地方(主要针对3中可能产生的问题)
写完之后表示:本来没想写这么长,越写越多,醉了。记录下来主要也是让自己查出一些知识漏洞
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新增了一个方法指向原来的方法
//提取几行关键的源码
IMP msgForwardIMP = _objc_msgForward;
....
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);
你可能会有疑问的地方,Answer见第7部分描述
Q:万一,原方法不存在怎么办?
Q:`_objc_msgForward`是什么鬼?
- 搞事第二步:
forwardInvocation
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "aaa@qq.com:@");
if (originalForwardImp) {
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "aaa@qq.com:@");
}
}
- 搞事第三步:
JPForwardInvocation
里到底做了啥?
经过搞事第一步我们知道,我们现在调用方法会直接进入消息转发机制的最后一步(以下是:包含关键步骤的不完整流程)。
一句话:获得NSInvocation所有参数,将消息转发给JS对应实现,获得JS回调结果,完成调用
-
Step01:判断调用的方法该类是否已实现,未实现则走 原方法指向的消息转发流程(上图中即
IMPforwardInvocation
)
JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);
if (!jsFunc) {
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}
Step02:处理调用函数的参数等相关信息
这里invocation会被利用,关于invocation,前文已经提及
这里涉及OC的type encoding(类型编码)Step03:通过
formatOCToJS
将参数数组转为对应的JS对象数组-
Step04:调用JS实现,传入之前处理的参数列表
主要涉及的方法:- (JSValue *)callWithArguments:(NSArray *)arguments;
你可能会有疑问的地方,Answer见第7部分描述Q:关于type encoding
推荐阅读
-
OFBiz进阶--OFBiz标签之[*Services.xml]-mililang介绍与分析(一)[
属性] -
Android开发中布局与组件(一)—— 屏幕尺寸单位dp,px,sp的探究
-
前端值得一探究竟特辑 --- 防抖与节流
-
前端值得一探究竟特辑 ---普通函数运行与构造函数运行(new背后的故事)
-
探究PHP中is_callable()与method_exists()函数
-
Method Swizzling与JSPatch探究(二)
-
Method Swizzling与JSPatch探究(一)
-
探究PHP中is_callable()与method_exists()函数
-
class反射(一),以及Method 的 getReadMethod 与 getWriteMethod 使用