Aspects 源码阅读

2017-06-17  本文已影响79人  ea9ae456d0de

Aspects 作为Object-C最受欢迎的AOP(面向切片编程)框架,提供在原方法执行之前(before)或者之后(after),指向某个动作,甚至于替换原方法(instead),主要在项目中用于将埋点和业务分离。

源码阅读

分别从 hm 文件去了解 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

当我们使用 Aspectsblock 嵌入某个方法后,如果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;

Aspects.m

.m 文件中4个类,一个结构体

_AspectBlock

定义 _AspectBlock 结构体是为了将 block 强转 为该类型的对象,以便从中获取到 blocksignature (方法签名)。其中要注意的是结构体中的 flags 成员,因为会根据 flags 计算出 signature偏移

// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
    AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
    AspectBlockFlagsHasSignature          = (1 << 30)
};

AspectInfo: NSObject(AspectInfo)

该类主要用于保存着方法的 Invocation 信息,如果 block 有参数,会作为 block第一个参数传给 block

AspectIdentifier

一个简单切片,包含着block

AspectIdentifier 在初始化的时会调用 aspect_isCompatibleBlockSignature 方法,会去检验 block 和要切的 selector 是否兼容?

  1. block的参数个数是否大于原方法的参数个数

  2. block的第一个参数是否是NSObject类型(@encode(@))

    第一个参数一定要是NSObject,因为block有参数的话,会将 AspectInfo 对象当做第一个参数传给block。

  3. 从第二个参数开始判断block的参数类型和原方法的参数类型是否一样

    block的第一个参数是block对象,第二个参数是AspectInfo,而原方法的参数中,第一个会是调用的对象,第二个是SEL,所以从下标为2的参数开始对比。

如果满足了上面的条件,block才会被切入方法中。

- (BOOL)invokeWithInfo:(id(AspectInfo))info

调用 block 的方法,主要是将为 blockInvocation 传递参数,然后触发 block

AspectsContainer

是存放 AspectIdentifier 的容器,以 aliceSelectorkey ,通过 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 是否可以被追踪

  1. 不允许 hook 黑名单中的方法
  2. 如果 hook dealloc 方法,option 必须为 AspectPositionBefore
  3. 是否能响应该方法
  4. 是否是类对象,如果是则判断继承体系中方法是否已经被Hook,而实例则不用

2. 根据self(实例或者类) 和 selector 找到对应的容器

aspect_getContainerForObject(self, selector);

3. 根据block和selector生成AspectIdentifier

主要判断 block 和 selector 的方法签名

  1. 判断参数个数
  2. 参数的类型

4. 如果生成AspectIdentifier成功,加入到容器中

5. aspect_prepareClassAndHookSelector,开始hook方法

5.1 aspect_hookClass

获取要进行 method swizzling 的类

  1. 如果该类的前缀是 AspectsSubclassSuffix ,说明对应 instancel 已经生成了子类并且 instance 的 isa也指向了子类,可以直接返回。

    有前缀,说明该类已经 hookforwardInvocation 方法了。

  2. 如果是元类,判断是否已经 hookforwardInvocation 方法了,如果 hook 过,就直接返回,否则 hook 后在返回。
  3. 如果 self.class != object_getClass(self) ,所以对象的 isa 已经改变了(KVO),对 object_getClass(self)forwardInvocation 进行 hook
  4. 对于 instance 生成类的子类,然后 hook forwardInvocation改变 instance 的isa指针
5.2 method swizzling
  1. 为上步获取到的class,添加 aliceSelector 方法,并将原 selector 的 IMP(现实),赋值给 aliceSelector
  2. 将原 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)];
}

上一篇下一篇

猜你喜欢

热点阅读