最近在做项目的打点统计的时候,为了使业务逻辑和打点逻辑分离,用到了一个开源的框架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的方法出发主体函数的执行,从而回到程序的原来位置,从而完成整个流程的执行。