美文网首页
iOS AOP框架Aspects实现原理

iOS AOP框架Aspects实现原理

作者: Crazy2015 | 来源:发表于2019-05-06 21:42 被阅读0次

    Aspects主要是利用了forwardInvocation进行转发,Aspects其实利用和kvo类似的原理,通过动态创建子类的方式,把对应的对象isa指针指向创建的子类,然后把子类的forwardInvocation的IMP替成ASPECTS_ARE_BEING_CALLED,假设要hook的方法名XX,在子类中添加一个Aspects_XX的方法,然后将Aspects_XX的IMP指向原来的XX方法的IMP,这样方便后面调用原始的方法,再把要hook的方法XX的IMP指向_objc_msgForward,这样就进入了消息转发流程,而forwardInvocation的IMP被替换成了ASPECTS_ARE_BEING_CALLED,这样就会进入ASPECTS_ARE_BEING_CALLED进行拦截处理,这样整个流程大概结束。

    // 动态生成子类 把子类的方法替换成自己的
    aspect_hookClass

            // Default case. Create dynamic subclass. 
        const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
        Class subclass = objc_getClass(subclassName);
    
        if (subclass == nil) {
            subclass = objc_allocateClassPair(baseClass, subclassName, 0);//(subclassName子类名_加后缀_Aspects_)
            if (subclass == nil) {
                NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
                AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
                return nil;
            }
    
      // 把子类的 forwardInvocation 指向 __ASPECTS_ARE_BEING_CALLED__
     // 替换子类的 forwardInvocation 方法
    aspect_swizzleForwardInvocation(subclass);
    
    

    ======

        // 由于新创建的class没有实现 forwardInvocation  因此 这里的 originalImplementation就是空的
        // 使用 __ASPECTS_ARE_BEING_CALLED__ 替换子类的 forwardInvocation 方法实现
        // 由于子类本身并没有实现 forwardInvocation ,
        // 所以返回的 originalImplementation 将为空值,所以子类也不会生成 AspectsForwardInvocationSelectorName 这个方法
    
    static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
    static void aspect_swizzleForwardInvocation(Class klass) {
        NSCParameterAssert(klass);
        // If there is no method, replace will act like class_addMethod.
        IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
        if (originalImplementation) {
            class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
        }
        AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
    }
    

    // 修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回当前对象的 class 隐藏对外的Class 类似KVO
    aspect_hookedGetClass(subclass, statedClass); subclass 动态生成的子类
    aspect_hookedGetClass(object_getClass(subclass), statedClass);

    static void aspect_hookedGetClass(Class class, Class statedClass) {
        NSCParameterAssert(class);
        NSCParameterAssert(statedClass);
        Method method = class_getInstanceMethod(class, @selector(class));
        IMP newIMP = imp_implementationWithBlock(^(id self) {
            return statedClass;
        });
        class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
    }
    

    第一部分主要是动态生成一个子类,然后把子类的forwardInvocation函数动态替换成ASPECTES_ARE_BEING_CALLED,然后通过object_setClass把self的isa指针指向subClass,这样后期调用的时候,会直接进入到动态创建的这个子类中来

        // 被 hook 的 selector
        Method targetMethod = class_getInstanceMethod(klass, selector);
        IMP targetMethodIMP = method_getImplementation(targetMethod);
    
        // 判断需要被Hook的方法是否应指向 _objc_msgForward 进入消息转发模式
        if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
            // Make a method alias for the existing method implementation, it not already copied.
            // 让一个新的子类方法名指向原先方法的实现,处理回调
            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.
            class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
            AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
        }
    

    第二部分aspect_isMsgForwardIMP是会提前进行判断原方法是否有被Hook走,有的话就不会再操作(这里和JSPatch会冲突),然后有两部操作,第一步把动态生成的带有前缀的方法的IMP指向原方法的实现,第二步把原来函数的IMP指针指向objc_msgForward直接进入消息转发流程

    最终Hook到以下函数,执行完之后回调到原来的方法实现,到此所有流程就结束了

    // This is the swizzled forwardInvocation: method.
    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);
        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.
        BOOL respondsToAlias = YES;
        if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
            aspect_invoke(classContainer.insteadAspects, info);
            aspect_invoke(objectContainer.insteadAspects, info);
        }else {
            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)
        if (!respondsToAlias) {
            invocation.selector = originalSelector;
            SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
            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)];
    }
    

    该函数为swizzle后, 实现新IMP统一处理的核心方法 , 完成一下几件事

    处理调用逻辑, 有before, instead, after, remove四种option
    将block转换成一个NSInvocation对象以供调用
    从AspectsContainer根据aliasSelector取出对象, 并组装一个AspectInfo, 带有原函数的调用参数和各项属性, 传给外部的调用者 (在这是block) .
    调用完成后销毁带有removeOption的hook逻辑, 将原selector挂钩到原IMP上, 删除别名selector

    1.找到被 hook 的 originalSelector 的 方法实现

    2.新建一个 aliasSelector 指向原来的 originalSelector 的方法实现

    3.动态创建一个 originalSelector 所在实例的子类,然后 hook 子类的 forwardInvocation: 方法并将方法的实现替换成 ASPECTS_ARE_BEING_CALLED 方法

    4.originalSelector 指向 _objc_msgForward 方法实现

    5.实例的 originalSelector 的方法执行的时候,实际上是指向 objc_msgForward ,而 objc_msgForward 的方法实现被替换成ASPECTS_ARE_BEING_CALLED 的方法实现,也就是说 originalSelector 的方法执行之后,实际上执行的是__ASPECTS_ARE_BEING_CALLED 的方法实现。而 aliasSelector 的作用就是用来保存 originalSelector 的方法实现,当 hook 代码执行完成之后,可以回到 originalSelector 的原始方法实现上继续执行

    1.找到被 hook 的 originalSelector 的 方法实现

    2.新建一个 aliasSelector 指向原来的 originalSelector 的方法实现

    3.动态创建一个 originalSelector 所在实例的子类,然后 hook 子类的 forwardInvocation: 方法并将方法的实现替换成 ASPECTS_ARE_BEING_CALLED 方法

    4.originalSelector 指向 _objc_msgForward 方法实现

    5.实例的 originalSelector 的方法执行的时候,实际上是指向 objc_msgForward ,而 objc_msgForward 的方法实现被替换成ASPECTS_ARE_BEING_CALLED 的方法实现,也就是说 originalSelector 的方法执行之后,实际上执行的是__ASPECTS_ARE_BEING_CALLED 的方法实现。而 aliasSelector 的作用就是用来保存 originalSelector 的方法实现,当 hook 代码执行完成之后,可以回到 originalSelector 的原始方法实现上继续执行


    作者:Deft_MKJing宓珂璟
    来源:CSDN
    原文:https://blog.csdn.net/Deft_MKJing/article/details/79567663
    版权声明:本文为博主原创文章,转载请附上博文链接!

    相关文章

      网友评论

          本文标题:iOS AOP框架Aspects实现原理

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