Method Swizzling与JSPatch探究(二)

以下笔记整理于2017-03-22
当时作为无埋点预研的学习整理,由于小组是客户端和前端混合的小组,所以下面尽可能以偏简单的文字在组内分享。
第一部分地址为:Method Swizzling与JSPatch探究(一)


4、兼容性

以hook UIViewControllerviewWillAppear的方法为例

4.1 仅method Swizzling, 无JSPatch
`ViewController:UIViewController`

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"HELLO 01");
    [super viewWillAppear:animated];
    NSLog(@"HELLO 02");
}

UIViewController+MLPF.m
- (void)xxx_viewWillAppear:(BOOL)animated {
    NSLog(@"HELLO Swizzle01");
    [self xxx_viewWillAppear:animated] ;
    NSLog(@"HELLO Swizzle02");
}

Output:
HELLO 01
HELLO Swizzle01
HELLO Swizzle02
HELLO 02
  • IMP变化:注意hook的是UIViewController的viewWillAppear而不是ViewController

  • 调用路径:

4.2 method Swizzling和JSPatch一起,JSPatch覆盖

为了避免swizzle在runtime期间出现诡异的异常,一般会写在+load里面,先于JSPatch加载

  • JSPatch,js文件,覆盖原方法
defineClass("ViewController", {
    viewWillAppear: function(animated) {
        console.log("JSPatch 01");
        self.super().viewWillAppear(animated);
        console.log("JSPatch 02");
    }
}, {});

output:
JSPatch.log: JSPatch 01
HELLO Swizzle01
HELLO Swizzle02
JSPatch.log: JSPatch 02
  • IMP变化:在之前的前一步的基础上

  • 调用路径

4.3 method Swizzling和JSPatch一起,JSPatch调用原方法
defineClass("ViewController", {
    viewWillAppear: function(animated) {
        console.log("JSPatch 01");
        self.ORIGviewWillAppear(animated);
        console.log("JSPatch 02");
    }
}, {});

Output:
JSPatch.log: JSPatch 01
HELLO 01
HELLO Swizzle01
HELLO Swizzle02
HELLO 02
JSPatch.log: JSPatch 02
  • IMP变化:与前一步一致
  • 调用路径:相对之前略复杂,因此打印的地方也附上
4.4 重复hook同一个方法,无JSPatch

重复hook:两处地方都对UIViewController的viewWillAppear进行method swizzle
hook顺序:先UIViewController+MLPF,后UIViewController+MLPF2

`ViewController:UIViewController`

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"HELLO 01");
    [super viewWillAppear:animated];
    NSLog(@"HELLO 02");
}

UIViewController+MLPF.m
- (void)xxx_viewWillAppear:(BOOL)animated {
    NSLog(@"HELLO Swizzle1-1");
    [self xxx_viewWillAppear:animated] ;
    NSLog(@"HELLO Swizzle1-2");
}

UIViewController+MLPF02.m
- (void)xxx02_viewWillAppear:(BOOL)animated {
    NSLog(@"HELLO Swizzle2-1");
    [self xxx02_viewWillAppear:animated];
    NSLog(@"HELLO Swizzle2-2");
}

Output:
HELLO 01
HELLO Swizzle2-1
HELLO Swizzle1-1
HELLO Swizzle1-2
HELLO Swizzle2-2
HELLO 02
  • IMP变化:为什么这样改变前文已经说明

  • 调用路径:

4.5 重复hook同一个方法和JSPatch的兼容性,JSPatch调用原方法

在4.4的基础上增加JSPatch,调用原方法

defineClass("ViewController", {
    viewWillAppear: function(animated) {
        console.log("JSPatch 01");
        self.ORIGviewWillAppear(animated);
        console.log("JSPatch 02");
    }
}, {});

output:
JSPatch.log: JSPatch 01
HELLO 01
HELLO Swizzle2-1
HELLO Swizzle1-1
HELLO Swizzle1-2
HELLO Swizzle2-2
HELLO 02
JSPatch.log: JSPatch 02
  • IMP变化:

  • 调用路径:经过前面几步的分析,这里实际上没什么难度

4.6 重复hook同一个方法和JSPatch的兼容性,JSPatch覆盖原方法

这里就不作累述了,经过前面的分析,个人觉得这部分直接给结果应该看得懂,因此这部分流程图就不画了

defineClass("ViewController", {
    viewWillAppear: function(animated) {
        console.log("JSPatch 01");
        self.super().viewWillAppear(animated);
        console.log("JSPatch 02");
    }
}, {});

JSPatch.log: JSPatch 01
HELLO Swizzle2-1
HELLO Swizzle1-1
HELLO Swizzle1-2
HELLO Swizzle2-2
JSPatch.log: JSPatch 02
4.7 一点补充

hotfix的代码不要出现如下情况,调用原实现,又调super,原实现代码规范是存在调super的,这样会导致重复调用,在4.6的基础上如下试验

defineClass("ViewController", {
    viewWillAppear: function(animated) {
        console.log("JSPatch 01");
        self.ORIGviewWillAppear(animated);
        self.super().viewWillAppear(animated);
        console.log("JSPatch 02");
    }
}, {});

JSPatch.log: JSPatch 01
HELLO 01
HELLO Swizzle2-1
HELLO Swizzle1-1
HELLO Swizzle1-2
HELLO Swizzle2-2
HELLO 02
HELLO Swizzle2-1
HELLO Swizzle1-1
HELLO Swizzle1-2
HELLO Swizzle2-2
JSPatch.log: JSPatch 02
5、结论

实际上,理解了2、3对method swizzling 和 JSPatch hook的原理分析,第四部分的兼容性实验的结果大致也能猜的八九不离十。
因为两者不存在冲突的地方,唯一的重合点就是对函数实现指针的替换
两者共用时,hotfix的方法和method Swizzling hook的函数实现都会按顺序调用,并不会出现覆盖,或者出错的情况

下一步试验:
1、实际上Method Swizzling还是存在一些坑的,上面是常规的实验。后续会更深入一些
2、同时试验以属性的形式KVO监听,进行埋点试验(目前试验了一点,但是case还不全)

6、拓展

当前AOP热门的库Aspects,在hook方案上,跟JSPatch大同小异,但是最后一步在forwardinvocation,Aspects则是保存block为关联属性。与JSPatch共用时在一些case下会产生crash 或者 覆盖执行等问题

关于Aspects和JSPatch兼容性的情况,这篇文章已经解释的较为详细,可供参考
http://www.jianshu.com/p/dc1deaa1b28e

7、也许你有疑问的地方:

主要针对第3部分的问题


Q:万一,原方法不存在怎么办?

A:实际上JSPatch源码实现,会调一个defineClass函数,这个函数最后都会调用overrideMethod方法,而overrideMethod会根据原类是否已定义过这个函数,来决定执行class_addMethod还是class_replaceMethod
如果是add那很简单,根本不存在是否兼容性的问题,所以并没有在上文指出


Q:_objc_msgForward是什么鬼?

A:一个函数指针。发送消息的时候,其实相当于objc_msgSend()执行的过程,这个函数会从Class的缓存中查找IMP,没找到则向父类Class查找,一直到根类都没找到,则会用_objc_msgForward函数指针替代IMP。而_objc_msgForward会尝试进行消息转发


贴一点runtime的伪代码,证明下这不是瞎逼逼

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(cls, sel, nil, 
                         YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

Q:关于type encoding

A:为了协助运行时系统,编码器用字符串为每个方法的返回值和参数类型和方法选择器编码。官方文档:

To assist the runtime system, the compiler encodes the return and argument types for each method in a character string and associates the string with the method selector.

8、Last

有问题欢迎讨论
2017-03-22



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值