美文网首页
Aspects 源码阅读

Aspects 源码阅读

作者: ea9ae456d0de | 来源:发表于2017-06-17 23:58 被阅读79次

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

    • 基于 Aspects (1.4.1) 分析

    源码阅读

    分别从 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;
    
    • 类方法会将block添加到,以 class 、 aliasSelector 找到的 AspectsContainer (容器) 对象的对应数组中
      无论class创建出来的哪个对象,执行selector时,都会触发 AspectsContainer(容器) 中的block。
    • 实例方法会将block添加到,以 instance 、 aliasSelector 找到的 AspectsContainer 对象的对应数组中
      那个对象调用aspect_hookSelector方法, block就只会跟哪个对象有关,不会被别的对象调用 selector是,触发block
    • 共性(类方法)和特里(实例方法)

    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

    • arguments:原方法的参数
    • originalInvocation:原方法的 Invocation , 可以从中获取到 参数。

    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)];
    }
    
    

    相关文章

      网友评论

          本文标题:Aspects 源码阅读

          本文链接:https://www.haomeiwen.com/subject/oaabqxtx.html