如何使用 Aspect 库来实现 Hook 一些函数。
#import "ViewController.h"
#import <Aspects/Aspects.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 下面只是官方提供的一个demo例子 https://github.com/steipete/Aspects
// API其实很简单,这里我们使用需要注意几点:
// 1. aspect_hookSelector 这个函数对 NSObject 基类做了分类拓展,支持 实例方法 和 类方法 。
// 2. withOptions 参数有
// ^ AspectPositionAfter = 0, /// Called after the original implementation (default) 原函数先调用,Hook函数后调用。
// ^ AspectPositionInstead = 1, /// Will replace the original implementation. Hook函数代替原函数,意味着选择这个参数。
// ^ AspectPositionBefore = 2, /// Called before the original implementation. 原函数后调用,Hook函数先调用。
// ^ AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution. 只对目标函数Hook一次。
// 3. usingBlock 参数有 1.id<AspectInfo> + 2.你Hook的函数参数,比如你Hook的viewWillAppear函数,有BOOL类型的Animation参数,所以
// usingBlock 也要带上这个BOOL参数。
// 4. **** Aspect 明确说明了,它不支持对静态函数的Hook,也就是我们的类方法 ****
// 虽然我们可以用 ViewController类 和 ViewController实例 都可以 Aspect Hook 函数,但是其Hook的函数都是 实例方法 不能Hook到静态方法 下面引入下Aspect注释
// @note Hooking static methods is not supported. 当然也实际测试了,确实如此。
//
id<AspectToken> token = [ViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animation) {
NSLog(@"hook viewWillAppear");
} error:NULL];
// 移除对目标函数的Hook
// [token remove];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
@end
说实话,我一开始是想看别人的原理解读,再来自己看原理的,后来发现,大部分文章太啰嗦,讲了一堆没有说核心部分,我想为了以后我自己再看不懵逼,也为了帮助别人去了解,我准备用图解的方式,直接了当的方式来表达核心实现原理。
下图描述的是他的核心功能如何实现的,其实还包括很多铺垫,比如检查方法 hook 的 block 方法签名与被 hook 的方法签名能否兼容,参数类型等,还有一些吧,在我都看完的情况下,如果全部展开会让大家感觉很难看下去,因为文字组织确实需要一些功力的,否则后面自己看都费劲,所以就可核心的展示,剩下注释我都写在 Demo 中,有兴趣可以看下。
核心逻辑 ASPECTS_ARE_BEING_CALLED 方法
// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
// 这里解释一下,为什么会执行到这里。
// 因为 aspect 替换了原函的实现,换成objc_msgForward,所以再调被hook函数会好触发消息转发流程
// 又因为 aspect 用 __ASPECTS_ARE_BEING_CALLED__ 替换了 消息转发流程中的 forwardInvocation 函数
// 所以就调用到这里面来了
// 断言参数 self invocation 不为空
NSCParameterAssert(self);
NSCParameterAssert(invocation);
// 获取 invocation 的SEL方法名
SEL originalSelector = invocation.selector;
// 拼接 别名方法 通过 SEL
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
// 替换invocation中的调用方法
invocation.selector = aliasSelector;
// 通过aspect别名方法获取关联对象 AspectsContainer
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
// 通过aspect别名方法获取关联对象 AspectsContainer
// object_getClass(self) 是 self的isa指向的位置,如果 self 是实例 就指向 类,如果 self 是类 就指向 元类。
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
// 创建 AspectInfo 对象通过 self 和 invocation
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
// 创建 aspectsToRemove变量 待用
NSArray *aspectsToRemove = nil;
// Before hooks.
// 从名字就应该可以猜出,这是我们挂的回调,option = before 在原函数执行之前执行。
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) { // 如果 option=instead替换 走这里
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else {
// 这里解释一下,这里是调用原函数的实现,如果你看了上面的图解,你就可以完全看懂下面的代码。
// 获取invoke对象
Class klass = object_getClass(invocation.target);
// 如果可以响应 别名函数,循环调用。
// 这里invoke是调用的原函数
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks.
// 这里是 option = after 的hook 被调用
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// If no hooks are installed, call original implementation (usually to throw an exception)
// 如果不响应别名函数,调用原函数实现。
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
if ([self respondsToSelector:originalForwardInvocationSEL]) { // 可以响应原函数
// 用 objc_msgSend 给自己发送 originalForwardInvocationSEL 消息携带参数 invocation
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
// 无法响应原函数,调用不识别方法。
[self doesNotRecognizeSelector:invocation.selector];
}
}
// Remove any hooks that are queued for deregistration.
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}