面向切面编程之Aspects源码解析

       最近在做项目的打点统计的时候,为了使业务逻辑和打点逻辑分离,用到了一个开源的框架Aspects。这个框架是基于swizzling method来实现的。这个库的代码量比较小,总共就一个类文件,使用起来也比较方便,比如你想统计某个controller的viewwillappear的调用次数,你只需要引入Aspect.h头文件,然后在合适的地方初始化如下代码即可。

pragma mark - addKvLogAspect

- (void)addKvLogAspect {
    //想法tab打开
    [self aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
       //统计打点
        NSLog(@"");
    }error:NULL];
}
       这篇文章主要是对aspect源码以及思路的解读。对swizzling method不了解的同学可以先去网上了解一下,下面的内容是基于大家对swizzling method有一定的了解的基础上的。

       从头文件中可以看到使用aspects有两种使用方式,1)类方法 2)实例方法

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

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;
两者的主要原理基本差不多,这里不做一一介绍,只是以实例方法为例进行说明。在介绍之前,先介绍里面几个重要的数据结构:

1)AspectOptions

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.
};
这里表示了block执行的时机
2)AspectsContainer:对象或者类的Apects整体情况
// Tracks all aspects for an object/class.
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end
3)AspectIdentifier:一个Aspect的具体内容
// Tracks a single aspect.
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end
4)AspectInfo:一个Aspect的基本信息
@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end

有了上面的了解,我们就能更好的分析整个apsects的执行流程。

1 使用aspects,首先要添加一个aspect。可以通过上面介绍的类/实例方法,添加方法里面的具体实现是调用了如下的方法:

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
   ...
    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {//1
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);//2
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];//2
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];//4

                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);//5
            }
        }
    });
    return identifier;
}

我们对上面代码逐步进行分析:

1)第一步判断是否能够hook:对于实例方法,这里主要是根据黑名单,比如 retain forwardInvocation等这些方法不能hook,(对于类方法还要确保同一个类继承关系层级中,只能被hook一次,因此这里需要判断子类,父类有没有被hook)。如果能够hook继续下面的步骤。

2)-4)这里是hook管理过程中需要用到的一些数据结构。如果能够hook,那么生成相应的数据结构,并保留起来

5)这里是重点,这里将会真正用到swizzling method。对传进来的selector进行swizzling。具体见代码

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    Class klass = aspect_hookClass(self, error);//1
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {//2
        // 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]) {//3
            __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);//4
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}
过程如下:
5.1)动态生成一个self的子类,并替换子类的forwardInvocation方法。这里的aspect_swizzleForwardInvocation做了一个保护,如果kclass已经实现了forwardInvocation则进行交换,否则直接替换。
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 是否已经生成过swizzling 子类
	if ([className hasSuffix:AspectsSubclassSuffix]) {
		return baseClass;

        // We swizzle a class object, not a single object.
	}else if (class_isMetaClass(baseClass)) {
        return aspect_swizzleClassInPlace((Class)self);
        // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
    }else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    // 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);
		if (subclass == nil) {
            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }

		aspect_swizzleForwardInvocation(subclass);//swizzling forwardinvation方法
		aspect_hookedGetClass(subclass, statedClass);//子类的class方法返回当前被hook的对象的class
		aspect_hookedGetClass(object_getClass(subclass), statedClass);//
		objc_registerClassPair(subclass);
	}

	object_setClass(self, subclass);//将当前self设置为子类,这里其实只是更改了self的isa指针而已
	return subclass;
}

5.2)-5.3判断被hook的selector是否是被转发了。如果不是被转发的imp(_objc_msgForward/_objc_msgForward_stret),对selector方法进行替换,selector指向了转发imp,aliasSelector指向了selector真正的实现。因此正常情况下,self的selector第一次被hook的时候,selector,aliasSelector对应的实现会进行交换,第二次重复hook的时候,其实由于selector的实现变成了转发imp,这里其实就不会再做交换了。

这里要解释一下,为什么要将selector指向_objc_msgForward。这里需要大概介绍一下消息的转发机制。我们知道oc是动态语言,我们执行一个函数的时候,其实是在发一条消息:[receiver message],我们根据receiver以及Message动态的找到需要执行的代码。但是当你向一个对象发送一条消息,而对象并没有实现的时候,就会触发对象的消息转发,也就是_objc_msgForward/_objc_msgForward_stret会尝试做消息转发。在消息转发的路径中,会有多个地方可以让你有机会对你的代码进行补救,其中一个地方就是forwardInvocation方法,你可以override这个方法,然后在这个方法中进行补救。在5.1中,我们说过,我们生成了一个动态子类,并swizzling了其forwardInvocation方法,由于我们将slector指向了(_objc_msgForward/_objc_msgForward_stret),可想而知,当selector被执行的时候,也会触发消息转发从而进入forwardInvocation,而我们又对forwardInvacation进行了swizzling,因此,最终转入我们自己的处理逻辑代码中

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)];
}
这里就是被hook的selector真正的处理流程了。在这里,我们根据AspectContainer里面的信息依次处理before hooks/instead hooks/after hooks。其处理函数aspect_invoke方法是根据AspectIdentifier里面block的签名,block主体等执行,使用invoke的方法触发block的执行。同时,如果selector的主体还存在,那么也将使用invoke的方法出发主体函数的执行,从而回到程序的原来位置,从而完成整个流程的执行。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值