Aspect 的代码很少,就两个文件,使用上也比较简单,提供了两个长差不多的方法,用来 Hook NSObject 的类方法和实例方法。
/// Adds a block of code before/instead/after the current `selector` for a specific class.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
上面这两个方法最终会调用同一个静态方法,因为类也可以看作是一个对象。
// for class
return aspect_add((id)self, selector, options, block, error);
// for instance
return aspect_add(self, selector, options, block, error);
理解对象 Aspect hook 流程
要理解一个开源库的使用,最简单的方式就是去看它的 TestCase,而不是一上来就看源码。
- (void)testMatchingBlockSignature {
// new TestClass
TestClass *testClass = [TestClass new];
__block BOOL called = NO;
// hook testCall 方法,并在原方法执行后,将 called 置为 YES
id aspect = [testClass aspect_hookSelector:@selector(testCall) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info) {
called = YES;
} error:NULL];
// 调用原方法
[testClass testCall];
}
因为源码不是特别复杂,不再特别展开,只描述下大致的流程
0x01 判断 selector 是否可以被 hook
Aspect 并不是支持 hook 所有的 selector,比如 @"retain", @"release", @"autorelease", @"forwardInvocation:" 这几个方法就不行。
不能在 hook dealloc 方法时使用 AspectPositionBefore。
0x02 保存 hook 时要执行的 block
为每一个被 hook 的对象,增加 AspectsContainer ,用来存储 hook 后要执行的 block。
0x03 生成被 hook 类的子类,替换子类的 forwarding 实现
使用 objc_allocateClassPair 和 objc_registerClassPair 方法,在运行时生成 TestClass_Aspects_ 类作为 TestClass 的子类。
使用 ASPECTS_ARE_BEING_CALLED 方法替换 TestClass_Aspects_ 的 forwardInvocation 方法。
替换 TestClass_Aspects_ 实例以及 metaClass 的 class 方法并返回 TestClass 用于欺骗外界,并将当前对象的 isa 指针指向 TestClass_Aspects_ ,这样能保证不影响其他 TestClass 对象的同时,只对 self 对象做 hook。
0x04 替换要被 hook 的 selector
给 subclass TestClass_Aspects_ 增加一个 aspect selector aspects__testCall ,并让这个 selector 调用 originIMP。
替换 subclass TestClass_Aspects_ 的原始 selector testCall ,并让这个 selector 调用 forwarding 方法。
到了这里,整个 hook 流程就结束了。
总结
对象的 block 比较简单,使用 KVO 这样的操作,动态创建一个 subclass 作为代理,使得不影响该 Class 的其他实例。
理解对象 hook 之后原始方法被调用的流程
在 hook 之后,调用原始的 selector,因为 self 对象已被置为 subclass TestClass_Aspects_ 的子类,该子类的原始 selector 方法又被替换为 forwarding 方法。
forwarding 方法也被替换为了 ASPECTS_ARE_BEING_CALLED 方法,因此会走到这个静态方法。
拿到关联在对象上的 container,以及关联在 metaClass 上的 contaienr,调用 container 内部中 fefore 类型的 block。
接着查看是否存在替换原本实现的 block,如果有就调用,如果没有则表明没有人替换原实现,需要调用原实现,因为原实现的 IMP 已经被替换为 aspects__testCall 的实现,因此只需要调用 aspects__testCall 方法即可。
这里有一个判断是,如果 subclass not respond 该 selector ,那么还应该通过 super 去不断查找。
最后调用 container 内部中 after 类型的 block。
理解 Hook 整个类的某方法
之前的流程是 hook 单个对象的某个方法,如果要 hook 某个类的所有该方法,流程上有点差异。
0x01 判断 selector 是否可以被 hook
Aspect 并不是支持 hook 所有的 selector,比如 @"retain", @"release", @"autorelease", @"forwardInvocation:" 这几个方法就不行。
不能在 hook dealloc 方法时使用 AspectPositionBefore。
Aspect 全局存储了被 hook 的 meta class,先看下该 meta class 是否已经被 hook,如果已经被 hook,会报错,A method can only be hooked once per class hierarchy。
接着查询是该 metaclass 要 hook 的 selector 是否已经被 super meta class hook,如果是则报错。
经过以上判断,此 meta class 没有被 hook 该 selector,创建 AspectTracker 存储该信息,并进行 hook。
AspectTracker 中不仅保存该 meta class 的 hook 信息,还保存其 super 类的 track 信息。
对于类对象还要确保同一个类继承关系层级中,只能被 hook 一次,因此这里需要判断子类,父类有没有被 hook,之所以做这样的实现,主要是为了避免出现死循环的出现
0x02 保存 hook 时要执行的 block
为每一个被 hook 的 meta class,增加 AspectsContainer ,用来存储 hook 后要执行的 block。
0x03 替换的 forwarding 实现
因为当前要 hook 的是类对象,因此不需要 subclass,只需要替换该类对象的 forwarding 实现即可。
创建 subclass 的目的也是为了隔离对象,保证只 hook 需要的对象。
0x04 替换 meta class 的原始 selector
给 meta class 增加一个 aspect selector ,并让这个 selector 调用 originIMP。
替换 meta class的原始 selector ,并让这个 selector 调用 forwarding 方法。
到了这里,整个 hook 流程就结束了。