Aspects使用方法
Aspects源码只有两个文件:Aspects.h和Aspects.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:,在此执行切面任务和原方法。