AOP框架Aspects原理解析

先说下我的读源码过程 , 总计花了1天时间 , 早上还是一脸懵 , 下午就是成竹在胸了

1 .先看了这2篇文章 , 知道了大体的思路, 找到了源码的骨头架子, https://www.jianshu.com/p/0d43db446c5b , https://blog.csdn.net/Deft_MKJing/article/details/79567663

2. 下载了源码, 对着思路看了源码,一边看,一边加注释 . 整体过了一遍源码 , 把骨架一步一步丰满起来 . 看源码很快, 源码也就不到1000行

3. 知道了原理后又看了这2篇很细的文章  , 讲的很细 , 验证了读源码时的一些思考和问题.  https://www.jianshu.com/p/dc9dca24d5de , https://www.jianshu.com/p/2ad7e90b521b

 

现在说下原理 . 众所周知,Aspects框架运用了AOP(面向切面编程)的思想,这里解释下AOP的思想:AOP是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

实现原理

其实主要涉及到两点,用过runtime的同学都知道方法swizzling和消息转发。

// 正常的方法调用会到objc_msgSend,如果对象无法响应这个方法就会调用_objc_msgForward走消息转发
id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    // 如果没找对应sel,这是imp为_objc_msgForward
    imp(self, op, ...); //调用这个函数,伪代码... 官方的objc_msgSend是汇编的
}
 
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发
    return imp;
}

消息转发

当向一个对象发送消息的时候,在这个对象的类继承体系中没有找到该方法的时候,就会Crash掉,抛出* unrecognized selector sent to …异常信息,但是在抛出这个异常前其实还是可以拯救的,Crash之前会依次执行下面的方法:

1. resolveInstanceMethod (或resolveClassMethod):实现该方法,可以通过class_addMethod添加方法,返回YES的话系统在运行时就会重新启动一次消息发送的过程,NO的话会继续执行下一个方法。

2. forwardingTargetForSelector:实现该方法可以将消息转发给其他对象,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。

3.1 methodSignatureForSelector:会去获取一个方法签名,如果没有获取到的话就回直接挑用doesNotRecognizeSelector,如果能获取的话系统就会创建一个NSlnvocation传给forwardInvocation方法。

3.2 forwardInvocation:该方法是上一个步传进来的NSlnvocation,然后调用NSlnvocationinvokeWithTarget方法,转发到对应的Target

4. doesNotRecognizeSelector:抛出unrecognized selector sent to …异常。

整体原理

上面讲了几种消息转发的方法,Aspects主要是利用了3.2 - forwardInvocation进行转发,Aspects的原理分为2种 ,

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

第二种就简单点, 针对类对象 , 直接使用了方法替换 , 替换原来的方法和新生成的方法, 然后调用新生成的方法会走到消息转发流程 , 也是在__ASPECTS_ARE_BEING_CALLED__拦截处理.

通过原理 , 也就知道了有这几个重要的点 .

1.如何生成子类 ,

2.替换了那些方法 ,

3.__ASPECTS_ARE_BEING_CALLED__的真正实现是什么 ,

了解了这3个点,方法的骨架就知道了

代码解析

代码解析这块的话主要讲下核心的模块,一些边边角角的东西去看了自然就明白了。

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            /// Called after the original implementation (default)
    AspectPositionInstead = 1,            /// Will replace the original implementation.
    AspectPositionBefore  = 2,            /// Called before the original implementation.

    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};

AspectOptions提供各种姿势,前插、后插、整个方法替换都行,简直就是爽。

static void aspect_performLocked(dispatch_block_t block) {
    static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
    OSSpinLockLock(&aspect_lock);
    block();
    OSSpinLockUnlock(&aspect_lock);
}

aspect_addaspect_remove方法里面用了aspect_performLocked, 而aspect_performLocked方法用了OSSpinLockLock加锁机制,保证线程安全并且性能高。不过这种锁已经不在安全,主要原因发生在低优先级线程拿到锁时,高优先级线程进入忙等(busy-wait)状态,消耗大量 CPU 时间,从而导致低优先级线程拿不到 CPU 时间,也就无法完成任务并释放锁。这种问题被称为优先级反转,有兴趣的可以点击任意门不再安全的 OSSpinLock

// 交换类的原方法的实现为_objc_msgForward 使其直接进入消息转发模式
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    // 得到要处理的类,可能是新生成的子类,也可能是原有类
    Class klass = aspect_hookClass(self, error);
    // 获取原有的method和imp
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    // 看这个imp是否是_objc_msgForward,如果不是,说明第一次hook
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        // 生成一个别名方法,方法的imp为原始方法的imp , 方便调用原始方法
        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);
        }

        // We use forwardInvocation to hook in.
        // 原始方法的imp设置为_objc_msgForward , 这样就会直接走消息转发流程
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}
// 生成子类或者直接替换类方法
static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);

    // Already subclassed , 已经hook过了 , 那就不需要hook了
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        return baseClass;

        // We swizzle a class object, not a single object.
        // 说明是self是一个类对象,替换方法,不生成子类
    }else if (class_isMetaClass(baseClass)) {
    
        return aspect_swizzleClassInPlace((Class)self);
        // Probably a KVO'ed class. 
       // Swizzle in place. Also swizzle meta classes in place.
       // 可能是一个被kvo处理过的对象,和类对象同样的处理方式,替换方法,不生成子类
    }else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    // Default case. Create dynamic subclass. 
    // 到这里说明是一个普通的实例对象 , 那就生成一个子类 ,然后使用isa swizzling
    // 生成子类的好处,只会针对这个实例对象hook,其他对象不受影响
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        // 生成一个类对 
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        if (subclass == nil) {
            // 生成类对失败,打印一下
            // objc_allocateClassPair failed to allocate class subclassName.
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }
        // 替换子类的forwardInvocation为__ASPECTS_ARE_BEING_CALLED__,并把原有类的forwardInvocation保存起来
        aspect_swizzleForwardInvocation(subclass);
        // 重写class 方法, 隐藏这个子类的存在
        aspect_hookedGetClass(subclass, statedClass);
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        // 注册类对
        objc_registerClassPair(subclass);
    }
    // isa swizzling , 把对象的isa指向这个新生成的子类
    object_setClass(self, subclass);
    return subclass;
}


// 不论上面走了那个if,都会调用下面的方法swizzling forwardinvation 方法
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    // 使用 __ASPECTS_ARE_BEING_CALLED__ 替换子类的 forwardInvocation 方法实现
    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));
}

aspect_hookClass这个方法,不论上面走了那个if,都会调用aspect_swizzleForwardInvocation方法把forwardInvocation的实现替换成__ASPECTS_ARE_BEING_CALLED__

 

现在当我们调用原来的方式时 , 就会走到forwardInvocation的实现中 ,现在来看看这个实现

// 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 invoke]时就会走到原始的方法实现
    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. 前面的hook调用
    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. hook后面的方法调用
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);

    // If no hooks are installed, call original implementation (usually to throw an exception) 如果原始方法和Instead的hook都没有调用,在试着调用原始方法,否则抛异常
    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.
    // 检查下是否有必要清除hook , 类别中有一种是AspectOptionAutomaticRemoval,自动移除的
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

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

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

现在看下 aspect_invoke(classContainer.beforeAspects, info);这个宏做了什么.

#define aspect_invoke(aspects, info) 
for (AspectIdentifier *aspect in aspects) {
    [aspect invokeWithInfo:info];
    if (aspect.options & AspectOptionAutomaticRemoval) { 
        aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect];
    } 
}


// 主要把NSInvocation中的参数放进去了block中,然后执行了block
- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    NSInvocation *originalInvocation = info.originalInvocation;
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;

    // Be extra paranoid. We already check that on hook registration.
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        AspectLogError(@"Block has too many arguments. Not calling %@", info);
        return NO;
    }

    // The `self` of the block will be the AspectInfo. Optional.
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    
	void *argBuf = NULL;
    for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
        const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
		NSUInteger argSize;
		NSGetSizeAndAlignment(type, &argSize, NULL);
        
		if (!(argBuf = reallocf(argBuf, argSize))) {
            AspectLogError(@"Failed to allocate memory for block invocation.");
			return NO;
		}
             // 把原来的originalInvocation中参数都放入到blockInvocation中
		[originalInvocation getArgument:argBuf atIndex:idx];
		[blockInvocation setArgument:argBuf atIndex:idx];
    }
    
    [blockInvocation invokeWithTarget:self.block];
    
    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}

 

总结

-forwardInvocation:的消息转发虽然看起来很神奇,但是平时没什么需求的话尽量不要去碰这个东西,一般的话swizzling基本可以满足 我们项目的大部分需求了 , 最后一张图解释下流程.


 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值