Aspects源码解析

518 阅读3分钟

Aspects使用方法

Aspects源码只有两个文件:Aspects.hAspects.m文件。使用的方式就是对NSObject添加了一个Category,其中有两个方法分别为类和对象添加切面block。

+ (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的执行时机有四种
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.
};

这里有一点需要注意,我们可以直接使用Objc(Runtime)的method_exchangeImplementations进行方法动态替换,但此方法只能针对类进行替换,这个类的所有实例对象都会受影响。而Aspects则支持针对instance(对象)的替换,这是一个特殊之处。

Aspects源码解析

想要深入理解Aspects的实现机制,阅读源码无疑是最好的方式。我打算从Aspects对外暴露的方法开始深入。

不管是替换类的方法,还是替换对象的方法,最终都会调用到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);
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

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

方法的返回值为AspectIdentifier的对象,从其类名和成员变量的声明上,我猜测这是一个Aspcet动作的描述类。

继续看aspect_performLocked方法

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

这个方法只是保证block任务是加锁顺序执行的,所以重点还是要看block里面是什么。

if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
    AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
    identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
    if (identifier) {
        [aspectContainer addAspect:identifier withOptions:options];

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

先执行aspect_isSelectorAllowedAndTrack看是否支持切面,有些方法是不支持切面的,retain、release、autorelease、forwardInvocation 这四个方法是不支持hook的。dealloc只支持beforeAspectPositionBefore这种option的hook。

继续看if{ }中的代码, 查看selector下是否有associated的AspectsContainer,如果有表示该方法被替换过,没有则创建新的。根据AspectsContainer类的定义,可知一个方法可以被多次hook,每一次hook都会包装成一个AspectIdentifier对象,并可以同时hook多种option状态,分别存在beforeAspects、insteadAspects、afterAspects里。

重点是aspect_prepareClassAndHookSelector

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    Class klass = aspect_hookClass(self, error);
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    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_hookClass方法

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
	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);
		aspect_hookedGetClass(subclass, statedClass);
		aspect_hookedGetClass(object_getClass(subclass), statedClass);
		objc_registerClassPair(subclass);
	}

	object_setClass(self, subclass);
	return subclass;
}

这里面的代码大概意思是: 如果替换的是类的方法 就是调用aspect_swizzleForwardInvocation替换forwardInvocation:方法的IMP为__ASPECTS_ARE_BEING_CALLED__,并将本来的forwardInvocation:添加成__aspects_forwardInvocation:

如果替换的是实例的方法

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);
		aspect_hookedGetClass(subclass, statedClass);
		aspect_hookedGetClass(object_getClass(subclass), statedClass);
		objc_registerClassPair(subclass);
	}

	object_setClass(self, subclass);

则动态创建类,并混淆instance的isa。

接着将原方法的实现替换为_objc_msgForward,调用的时候直接触发消息转发。调用到forwardInvocation:,在此执行切面任务和原方法。