SDMagicHook
SDMagicHook copied to clipboard
您这个和Aspects的instance hook有什么不同吗😂
在开源这份代码之前我并未了解和使用过aspect,发表完https://mp.weixin.qq.com/s/wxigL1Clem1dR8Nkt8LLMw 这篇技术文章之后看到有些留言提到了aspect然后我去github大概看了一下基本思路都是基于类似kvo的isa替换,但是从api设计以及实现上也有明确的区别。具体区别或者异同我最近先研究下aspect源码,随后会给出答案。
以及
还包括了统一hook时替换imp为objc_msgForward,hook forwardInvocation: 这种细节;
在开源这份代码之前我并未了解和使用过aspects,发表完https://mp.weixin.qq.com/s/wxigL1Clem1dR8Nkt8LLMw 这篇技术文章之后看到有些留言提到了aspects然后我去github大概看了一下基本思路都是基于类似kvo的isa替换,但是从api设计以及实现上也有明确的区别,我们通过以下示例简要介绍下:
假设有这样一个自定义类Test,在其内部定义了一个求和的方法,接收四个int类型的参数。
@implementation Test
- (int)sumWithA:(int)a b:(int)b c:(int)c d:(int)d {
return a + b + c + d;
}
@end
现在要求将四个参数分别平方然后再求和。
使用aspects实现如下:
Test *testObj = [Test new];
[testObj aspect_hookSelector:@selector(sumWithA:b:c:d:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info, int a, int b, int c, int d) {
int aa = a * a;
int bb = b * b;
int cc = c * c;
int dd = d * d;
[info.originalInvocation setArgument:&aa atIndex:2];
[info.originalInvocation setArgument:&bb atIndex:3];
[info.originalInvocation setArgument:&cc atIndex:4];
[info.originalInvocation setArgument:&dd atIndex:5];
} error:NULL];
int sum = [testObj sumWithA:1 b:2 c:3 d:4];
NSLog(@">>>> %d", sum); // >>>> 30
使用SDMagicHook实现如下:
Test *testObj = [Test new];
[testObj hookMethod:@selector(sumWithA:b:c:d:) impBlock:^(typeof(testObj) this, int a, int b, int c, int d) {
__block int res;
[this callOriginalMethodInBlock:^{
res = [this sumWithA:a * a b:b * b c:c * c d:d * d];
}];
return res;
}];
int sum = [testObj sumWithA:1 b:2 c:3 d:4];
NSLog(@">>>> %d", sum); // >>>> 30
由以上demo可以看出: 1.aspects使用AspectOptions来决定自定义方法和原始方法的执行顺序;SDMagicHook使用callOriginalMethodInBlock来调用原始方法,可以将原始方法放在自定义逻辑的前、中、后任意位置执行,更加灵活方便。
2.aspects将原始方法封装在NSInvocation里面,如果想要修改sumWithA:b:c:d:
的参数值需要调用setArgument:atIndex:方法来实现,api不够简洁友好;SDMagicHook只需在callOriginalMethodInBlock的block参数内部直接调用原始的sumWithA:b:c:d:
方法传参即可,直观简便。
其实触发方法转发走 forwardInvocation、替换 isa 指针、对象级别 Hook 等等这些都是老生常谈的Hook 技术,很多组件都基于这套方案来 Hook,在长时间使用后也暴露了很多已知的缺陷。我之前写的节流限频组件也是用到了这些技术,文章大部分都是介绍 Aspects 没有处理好的场景: http://yulingtianxia.com/blog/2018/07/31/MessageThrottle-Safety/
不过凡是基于方法转发的 Hook 方案都有一个无法解决的致命问题,就是父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环。这也是为何这个方案在多年之后 Aspects 的作者终于声明它不再适用于生产环境。
而此问题需要吴子奇基于桥的 Hook 在汇编层面来解决。
想问下除了原理基本相同,API 略有不同之外,有没有解决 Aspects 相关技术带来的这些通病呢?
其实触发方法转发走 forwardInvocation、替换 isa 指针、对象级别 Hook 等等这些都是老生常谈的Hook 技术,很多组件都基于这套方案来 Hook,在长时间使用后也暴露了很多已知的缺陷。我之前写的节流限频组件也是用到了这些技术,文章大部分都是介绍 Aspects 没有处理好的场景: http://yulingtianxia.com/blog/2018/07/31/MessageThrottle-Safety/
不过凡是基于方法转发的 Hook 方案都有一个无法解决的致命问题,就是父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环。这也是为何这个方案在多年之后 Aspects 的作者终于声明它不再适用于生产环境。
而此问题需要吴子奇基于桥的 Hook 在汇编层面来解决。
想问下除了原理基本相同,API 略有不同之外,有没有解决 Aspects 相关技术带来的这些通病呢?
关于你提到的第一个问题: “父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环”应该是在所有同一类型的对象共享同一个类kvo类指针造成的问题。SDMagicHook是每个对象单独享有一个自定义的新类,对象之间相互隔离互不影响。也就是说假如有这样一个继承关系A->B->C,现在C类有个实例c1(0xa0),B类有个实例b1(0xb0),c1和b1都hook了自己的test方法,那么会生成两个新类SDC_0xa0和SDB_0xb0,其继承关系如下A->B->C->SDC_0xa0、A->B->SDB_0xb0,具体的hook操作发生在SDC_0xa0和SDB_0xb0这两个新类上,原始的A类和B类不受任何影响SDC_0xa0和SDB_0xb0之间也相互隔离所以避开了你提到的这个问题。
关于你提到的Aspects本身遇到但是尚未解决的问题:在aspects的readme中看到了他们介绍到有“KVO works if observers are created after your calls aspect_hookSelector: It most likely will crash the other way around. Still looking for workarounds here - any help appreciated.”这样一个问题。SDMagicHook测试了一下同样遇到了,但是我们已经有了相关解决方案,大概会在下周一前后同步到github上。
还有其他问题的话欢迎持续交流反馈 @yulingtianxia
兼容系统KVO已经实现,欢迎更新&反馈
Hi @yulingtianxia
父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环
关于这个问题,我相信 https://github.com/623637646/SwiftHook 很好的解决了这个问题。