面向切面编程

Jul 29, 2015


介绍

Aspect Oriented Programming(AOP),面向切面编程,主要实现的目的是针对业务处理过程中的切面进行提取。它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。

主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

比如说我们在实现权限验证的时候,需要在每个业务的执行前对权限进行相应的判断,从而导致了大量的重复代码,不利于模块的复用。AOP则通过将每个业务的公共行为进行抽离,封装成一个可复用的模块,这个模块就叫做『切面』。

概念

  • Aspect: 对系统中的横切关注点逻辑进行模块化封装的AOP概念实体。
  • Join point: 一个程序的执行点, 比如方法执行或者处理一个异常。
  • Advice: aspect 中对切入点(join point)操作的封装, 相当于OOP中的method,不同的advice类型包括 “instead”, “before” 和 “after” advice。
  • Pointcut: 代表的是Jointpoint的表述方式, 将横切逻辑织入当前系统的过程中, 需要参照Pointcut规定的Joinpoint信息, 才可以知道应该往系统的哪些Joinpoint上织入横切逻辑。
  • Target object: 被一个或多个aspect横切拦截操作的目标对象。
  • AOP proxy: 由AOP框架生成的用于实现aspect相关的代理对象。
  • Weaving: 织入是把aspects里的横切逻辑连接到目标对象的过程。

advice 的类型:

  • Before advice: 在切入点(join point)之前执行的advice
  • After returning advice: 切入逻辑在切入点完全执行完之后再执行
  • After throwing advice: 切入逻辑执行当切入的方法抛出了异常
  • Around advice: 横切逻辑在切入点周围执行, 比如在一个方法执行的开始和结束,这是最强大的一种横切逻辑。around advice 可以完成在方法执行的开始和结束做些自定义操作. 它可以决定是选择执行切入点的逻辑或者直接返回横切逻辑的结果通过return或者抛出异常来。

iOS开源框架Aspects

使用

Aspects是iOS中一个轻量级的面向切面编程的库。它提供了三个切入点:before(在原始的方法前执行)/instead(替换原始的方法执行)/after(在原始的方法后执行),通过Runtime消息转发实现Hook。

A delightful, simple library for aspect oriented programming.

Think of Aspects as method swizzling on steroids. It allows you to add code to existing methods per class or per instance, whilst thinking of the insertion point e.g. before/instead/after. Aspects automatically deals with calling super and is easier to use than regular method swizzling.

Aspects扩展了NSObject类,提供了一个类方法和实例方法:

为指定类方法的before/instend/after添加block代码:

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

为指定实例方法的before/instend/after添加block代码:

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

上面两个方法会返回一个类型为AspectToken的token,可以在后面使用它的方法撤销掉添加的block代码。

id<AspectToken> aspect = ...;
[aspect remove];

Aspects使用的是runtime机制,有一定的开销,所以不能再调用次数很多的地方添加block代码。

内部原理分析

aspect_add方法:

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    NSCParameterAssert(block);
    //声明AspectIdentifier实例
    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        //判断当前XX方法是否允许被Hook,1."retain"、"release"、"autorelease"、"forwardInvocation"这几个方法是不被允许的,所谓的黑名单。2.如果方法是dealloc,则他的切入点必须是Before。3.判断当前实例对象和类对象是否能响应方法4.是否是类对象,如果是则判断继承体系中方法是否已经被Hook,而实例则不用。
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            //获得当前aspects__XX方法的AspectsContainer容器
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            //初始化AspectIdentifier变量,方法内部通过toll-free bridged获取Block方法签名,并判断其兼容性
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                //通过options选项分别添加到容器中的beforeAspects,insteadAspects,afterAspects这三个数组
                [aspectContainer addAspect:identifier withOptions:options];
 
                //HookSelector的过程和HookClass的过程
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

HookClass过程:

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);
 
    // 如果类名有_Aspects_前缀,说明Class已被Hook
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        return baseClass;
 
        // 判断是否为类对象,如果是,则直接在当前类中进行swizzle
    }else if (class_isMetaClass(baseClass)) {
        return aspect_swizzleClassInPlace((Class)self);
        // 判断是否为KVO过的对象,因为KVO的对象ISA指针会指向一个中间类,则直接在这个中间类中进行swizzle
    }else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }
 
    // 默认则会动态创建一个子类
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    Class subclass = objc_getClass(subclassName);
 
    if (subclass == nil) {
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        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__
        aspect_swizzleForwardInvocation(subclass);
        //实现当前类的isa指针指向原生的类
        aspect_hookedGetClass(subclass, statedClass);
        //实现当前类的元类的isa指针指向原生的类
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        //注册当前类
        objc_registerClassPair(subclass);
    }
    //将当前对象的isa指针指向刚生成的类
    object_setClass(self, subclass);
    return subclass;
}

HookSelector过程:

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    //HookClass过程
    Class klass = aspect_hookClass(self, error);
    //此时的klass类为刚创建的具有_Aspects_后缀的子类,在创建的时候指定类他的父类,所以我们可以获取到selector这个方法
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    //判断是否为消息转发
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        //获得原生方法的类型编码
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        if (![klass instancesRespondToSelector:aliasSelector]) {
            //为klass添加aspects__XX方法,方法的实现为原生方法的实现。
            __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);
        }
        // 将原生方法实现替换为_objc_msgForward或_objc_msgForward_stret,用来实现消息转发
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}

当我们正式向某个接受者发送消息的时候,会进行消息转发,而之前HookClass的过程当中我们已经对forwardInvocation的实现替换为了__ASPECTS_ARE_BEING_CALLED__

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    //获取原始方法XX
    SEL originalSelector = invocation.selector;
    //获取含有前缀的方法aspects_XX
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    //替换Sel
    invocation.selector = aliasSelector;
    //获得实例对象容器
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    //获得类对象容器
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    //初始化AspectInfo,传入self、invocation参数
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;
 
    // 执行before切入点的调用
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);
 
    // 执行Instead切入点的调用,判断当前insteadAspects是否有数据,如果没有数据则判断当前继承链是否能响应aspects_XX方法,如果能,则直接调用,此时的aspects_XX则为原生的实现。 
    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切入点的调用
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);
 
    // 若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方法
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

执行block的代码:

- (BOOL)invokeWithInfo:(id)info {
    //根据blockSignature获取Invocation
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    //获取原生方法的Invocation
    NSInvocation *originalInvocation = info.originalInvocation;
    //获取blockInvocation参数个数
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
 
    // 判断blockInvocation参数个数是否大于originalInvocation参数个数
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        AspectLogError(@"Block has too many arguments. Not calling %@", info);
        return NO;
    }
 
    // blockInvocation给索引为1的参数赋值
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    //当所以大于1的时候进行遍历,把原生的参数值赋值给相应的blockInvocation中的参数
    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 getArgument:argBuf atIndex:idx];
		[blockInvocation setArgument:argBuf atIndex:idx];
    }
    
    [blockInvocation invokeWithTarget:self.block];
    
    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}