Aspects 作为Object-C最受欢迎的AOP(面向切片编程)框架,提供在原方法执行之前(before)或者之后(after),指向某个动作,甚至于替换原方法(instead),主要在项目中用于将埋点和业务分离。
- 基于
Aspects (1.4.1)
分析
源码阅读
分别从 h
和 m
文件去了解 Aspects
中的各个协议或者对象。
Aspects.h
h文件中主要关注
AspectInfo
协议和了解为什么要弄两个入口(类方法和实例方法)
/// The AspectInfo protocol is the first parameter of our block syntax.
@protocol AspectInfo <NSObject>
/// The instance that is currently hooked.
- (id)instance;
/// The original invocation of the hooked method.
- (NSInvocation *)originalInvocation;
/// All method arguments, boxed. This is lazily evaluated.
- (NSArray *)arguments;
@end
当我们使用 Aspects
将 block
嵌入某个方法后,如果block是带参数的,那么第一个参数就是遵守AspectInfo协议的对象,通过这个对象,我们可以获取到原方法的参数。
在.m文件中,我们可以知道block的第一个参数是AspectInfo的实例。
// The `self` of the block will be the AspectInfo. Optional.
if (numberOfArguments > 1) {
// 如果block有参数,下标为1的参数为info
[blockInvocation setArgument:&info atIndex:1];
}
切片入口
@interface NSObject (Aspects)
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- 类方法会将block添加到,以
class 、 aliasSelector
找到的AspectsContainer
(容器) 对象的对应数组中
无论class创建出来的哪个对象,执行selector时,都会触发 AspectsContainer(容器) 中的block。 - 实例方法会将block添加到,以 instance 、 aliasSelector 找到的 AspectsContainer 对象的对应数组中
那个对象调用aspect_hookSelector方法, block就只会跟哪个对象有关,不会被别的对象调用 selector是,触发block - 共性(类方法)和特里(实例方法)
Aspects.m
.m 文件中4个类,一个结构体
_AspectBlock
定义 _AspectBlock
结构体是为了将 block
强转 为该类型的对象,以便从中获取到 block
的 signature
(方法签名)。其中要注意的是结构体中的 flags
成员,因为会根据 flags
计算出 signature
的偏移
// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature = (1 << 30)
};
AspectInfo: NSObject(AspectInfo)
该类主要用于保存着方法的 Invocation
信息,如果 block
有参数,会作为 block
的第一个参数传给 block
。
- arguments:原方法的参数
- originalInvocation:原方法的 Invocation , 可以从中获取到 参数。
AspectIdentifier
一个简单切片,包含着block
AspectIdentifier
在初始化的时会调用 aspect_isCompatibleBlockSignature
方法,会去检验 block
和要切的 selector
是否兼容?
-
block的参数个数是否大于原方法的参数个数
-
block的第一个参数是否是NSObject类型(@encode(@))
第一个参数一定要是NSObject,因为block有参数的话,会将 AspectInfo 对象当做第一个参数传给block。
-
从第二个参数开始判断block的参数类型和原方法的参数类型是否一样
block的第一个参数是block对象,第二个参数是AspectInfo,而原方法的参数中,第一个会是调用的对象,第二个是SEL,所以从下标为2的参数开始对比。
如果满足了上面的条件,block才会被切入方法中。
- (BOOL)invokeWithInfo:(id(AspectInfo))info
调用
block
的方法,主要是将为block
的Invocation
传递参数,然后触发block
。
AspectsContainer
是存放
AspectIdentifier
的容器,以aliceSelector
为key
,通过objc_setAssociatedObject
关联到class/instance
上,存储着嵌入 selector 的block数组 。
从.h文件入口,了解方法流程
无论是类方法还是实例方法,最终到会调用
aspect_add
静态方法,因此我们可以直接从这个方法入手
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
__block AspectIdentifier *identifier = nil;
// 加锁
aspect_performLocked(^{
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
// 创建aspect(方面/切面)
identifier = [AspectIdentifier identifierWithSelector:selector
object:self
options:options
block:block error:error];
if (identifier) {
// 如果创建identifier成功,将aspect添加到容器中
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
// 修改class,以允许消息拦截
/** method swizzling 和消息转发*/
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
1. aspect_isSelectorAllowedAndTrack 检查 selector 是否可以被追踪
- 不允许
hook
黑名单中的方法 - 如果
hook dealloc
方法,option
必须为AspectPositionBefore
- 是否能响应该方法
- 是否是类对象,如果是则判断继承体系中方法是否已经被Hook,而实例则不用
2. 根据self(实例或者类) 和 selector 找到对应的容器
aspect_getContainerForObject(self, selector);
3. 根据block和selector生成AspectIdentifier
主要判断 block 和 selector 的方法签名
- 判断参数个数
- 参数的类型
4. 如果生成AspectIdentifier成功,加入到容器中
5. aspect_prepareClassAndHookSelector,开始hook方法
5.1 aspect_hookClass
获取要进行
method swizzling
的类
- 如果该类的前缀是
AspectsSubclassSuffix
,说明对应instancel
已经生成了子类,并且 instance 的 isa也指向了子类,可以直接返回。有前缀,说明该类已经
hook
过forwardInvocation
方法了。 - 如果是元类,判断是否已经
hook
过forwardInvocation
方法了,如果hook
过,就直接返回,否则hook
后在返回。 - 如果 self.class != object_getClass(self) ,所以对象的 isa 已经改变了(KVO),对
object_getClass(self)
的forwardInvocation
进行hook
。 - 对于
instance
生成类的子类,然后hook forwardInvocation
,改变 instance 的isa指针。
5.2 method swizzling
- 为上步获取到的class,添加 aliceSelector 方法,并将原 selector 的 IMP(现实),赋值给 aliceSelector 。
- 将原 selector 的实现(IMP)改为 _objc_msgForward 或者 _objc_msgForward_stret。
当一个 selector 的实现为 _objc_msgForward 或者 _objc_msgForward_stret时,当调用 selecotr 是,会走消息转发流程,这就是手动触发消息转发。
ASPECTS_ARE_BEING_CALLED
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
SEL originalSelector = invocation.selector;
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
// 如果originalSelector被hook过,那么invocation的实际selector是aliasSelector
// 替换invocation的selector,为了[invocation invoke] 调用原来的方法
invocation.selector = aliasSelector;
// 获取绑定在self中的aliasSelector
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
// 获取绑定在Class中的aliasSelecor
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.
BOOL respondsToAlias = YES;// 判断select是否被调用了
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else {
// 没有Instead hooks时就执行selector 被hook之前的实现。
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks.
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// If no hooks are installed, call original implementation (usually to throw an exception)
/**调用一个没有实现的selector会触发 自动消息转发,在这种情况下整个继承链中都不会响应aliasSelector也就导致respondsToAlias=false, 开始执行下面的方法*/
if (!respondsToAlias) {
// 如果没有被调用,说明 originalSelector 没有被hook,所以将invocation.selector 改回来
invocation.selector = originalSelector;
// 原来的forwardInvocation
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
// 如果实现了forwardInvocation,执行原来的消息转发,否则调用doesNotRecognizeSelector,抛出异常。
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((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)];
}
网友评论