欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Method Swizzling与JSPatch探究(一)

程序员文章站 2022-04-10 20:59:28
...

以下笔记整理于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。简化版流程图如下:
Method Swizzling与JSPatch探究(一)
说明:

  • 发送消息,根据message生成selector
  • 根据selector寻找指向函数实现的指针IMP,存在直接执行
    不存在进入消息转发机制
  • resolveInstanceMethod动态方法解析-常用于给类/对象动态添加一个相应的实现
  • forwardingTargetForSelector备用接收者-将消息转发给其他对象处理
  • forwardInvocation完整转发-最后一次将消息转发给其他对象,包含一个表示消息的NSInvocation的对象,包含尚未处理的消息的全部细节,包括selectortargetparams
  • 以上都没完成转发,则doesNotRecognizeSelector,此时就会看到我们常见的unrecognize sent to selector balabala之类的错误
2、 Method Swizzling

method_exchangeImplementations为例

  • hook关键:未进入消息转发机制时,直接从IMP hook,交换2个方法的IMP

selORIG为原方法,IMPORIG为对应指向函数实现的指针

  • 原始方法
    Method Swizzling与JSPatch探究(一)

  • hook selORIG 一次
    Method Swizzling与JSPatch探究(一)

  • hook selORIG 两次,先A后B
    Method Swizzling与JSPatch探究(一)
    第二次hook,实质上已经跟selA无关,是selORIGselB对应的IMP的交换,因此selB对应IMPAselORIG对应IMPB。而selA不受影响仍然是IMPORIG

  • hook selORIG 三次,顺序为:A、B、C
    Method Swizzling与JSPatch探究(一)
    经过两次hook的解释,应该懂它的套路了

以上:不同的hook,是在不同的地方实现
先后顺序的保证:由于Method Swizzling都是写在+(void)load方法中的,compile Sources的顺序就是load方法的调用顺序(注意这里是已分类的形式的情况下,如果存在父类和子类同时swizzle,是先执行父类的+load,这也是一个坑-后续再讨论)

关于Method Swizzling如何实现,自行Google

3、JSPatch

相当普及的热修复库,虽然目前iOSer都收到苹果爸爸的警告邮件了,这个框架的未来走势还有待观察,但是智慧无穷,它总会存在。

一点说明

因为本文最后要讲Method SwizzlingJSPatch的兼容性。因此下文,不会涉及JS是如何和OC交互的?JSPatch很细节的实现是什么?只会涉及,它是如何hook,在哪一步hook

在哪一步&如何hook

白话文概括其原理:
1、 改变原指向函数实现的IMP指针为_objc_msgForward(或_objc_msgForwad_stret)
2、 执行方法,直接进入消息转发过程
3、 消息转发最后一步hook-forwardInvocation

下面用图来说明:

  • 搞事前:安然无恙
    Method Swizzling与JSPatch探究(一)


  • 搞事第一步:改变方法指向_objc_msgForward
    Method Swizzling与JSPatch探究(一)
    如上图:用过JSPatch都知道,我们可以通过self.ORIGtest()的方式调用原方法,是因为JSPatch新增了一个方法指向原来的方法
//提取几行关键的源码
IMP msgForwardIMP = _objc_msgForward;
....
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);


你可能会有疑问的地方,Answer见第7部分描述

    Q:万一,原方法不存在怎么办?  
    Q:`_objc_msgForward`是什么鬼?  



  • 搞事第二步:forwardInvocation
    Method Swizzling与JSPatch探究(一)
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