一. Aspects 是什么?
Aspect-oriented programming(AOP)
通常被我们称为面向切面编程
。有时候一些需求更需要面向切面编程
,而不是传统的面向对象编程
。比如以下几种情况:
- 无论何时,用户调用一个
API
必须进行安全检查。
- 所有
API
的调用都必须打印日志
- 总得来说就是
面向对象
要求我们类有单一职责原则
,而有时候业务需要
它们额外干一些和他们本职无关
的事情。
二. 源码解读,主要涉及四个方法。
1. aspect_add
: 是添加 hook 的主入口方法。
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
__block AspectIdentifier *identifier = nil;
// 1.block内代码加锁(OSSpinLock 自旋锁)
aspect_performLocked(^{
// 2.判断selector是否可以进行hook(比如retain是不允许的)
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
// 3.创建一个存储identifier的容器,并且把容器通过关联对象技术(objc_setAssociatedObject),关联到self上,这样self销毁的时候,也销毁Hook相关资源,防止内存泄露.
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
// 4.用传入参数构建一个AspectIdentifier对象,销毁释放hook的时候使用.
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
// 5.将AspectIdentifier对象加入到容器中
[aspectContainer addAspect:identifier withOptions:options];
// 6.核心代码:修改self的Class使得它能够方法劫持
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
2. aspect_prepareClassAndHookSelector
:第二个核心方法
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
// 6.1 hook改变self的Class类型.
Class klass = aspect_hookClass(self, error);
// 6.2 获取 selector 的 Method 对象
Method targetMethod = class_getInstanceMethod(klass, selector);
// 6.3 获取 selector 的 IMP
IMP targetMethodIMP = method_getImplementation(targetMethod);
// 6.4 判断如果不是消息转发的IMP
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
// Make a method alias for the existing method implementation, it not already copied.
// 6.5 为 selector 创建一个别名,并且 copy 它的实现
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
}
// We use forwardInvocation to hook in.
// 6.6 把 selector 的实现换成 _objc_msgForward,_objc_msgForward是一个函数地址,该函数执行会让selector的调用进入消息转发阶段.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
3. aspect_hookClass
:动态创建子类,并替换掉 forwardInvocation:
方法让消息转发进入 __ASPECTS_ARE_BEING_CALLED__()方法
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
// 6.1.1 拿到类对象
Class statedClass = self.class;
// 6.1.2 拿到类对象
Class baseClass = object_getClass(self);
// 6.1.3 类对象的字符串
NSString *className = NSStringFromClass(baseClass);
// Default case. Create dynamic subclass.
// 6.1.4 默认情况,创建动态子类
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
// 6.1.5 替换原有的forwardInvocation:方法,换成__ASPECTS_ARE_BEING_CALLED__(NSObject *self, SEL selector, NSInvocation *invocation)方法
aspect_swizzleForwardInvocation(subclass);
// 6.1.6 替换subclass的 class 方法,都返回 statedClass
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
// 6.1.7 把 self 的Class类型改成 subclass
object_setClass(self, subclass);
return subclass;
}
4.__ASPECTS_ARE_BEING_CALLED__
: 当我们 hook
的方法被调用时,就会来到这个方法。会根据我们配置的参数来调用我们 hook
添加的 block
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
SEL originalSelector = invocation.selector;
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
invocation.selector = aliasSelector;
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// Before hooks. 调用
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks. 调用
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
// After hooks. 调用
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
}
三. Aspects
无法解决和 KVO 共存
的问题,作者已经不推荐大家在生产环境中使用 Aspects
了。
- 使用
字节跳动团队
开源的 SDMagicHook
,它们的目的就是取代 Aspects
- 他们通过深入研究
KVO
的本质,解决了 兼容KVO
的问题。
- 不过我觉得他们的
hook
逻辑为了处理 KVO
问题,变得相当复杂,本身 hook
就是一个比较不让人省心的做法
,所以一旦出现问题会比较难调试。
- 仁者见仁智者见智的使用吧。