开源代码信息

源码地址

在编程中有两种比较重要的扩展方式,一种是面向对象编程中的继承方式,一种是面向切面编程,前者是纵向扩展,而后者是横向扩展,目前大多数的流行编程语音都有这两种扩展方式,在iOS开发中使用Aspect来对现有代码进行横向扩展,Aspect用于在当前selector之前或者之后插入代码块或者使用某个代码快替换当前selector,它通过OC的消息转发机制hook消息。所以会有一些性能开销,建议不要把Aspects加到经常被使用的方法里面。

源码解析

1. Aspect对外接口

Aspect 代码比较精炼,就两个文件两个接口,我们先来看下它对外的接口:

+ (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;

Aspects是NSObject的一个扩展,所以原则上只要是NSObject的实例都可以通过上面两个方法进行hook。
第一个参数selector是当前类需要hook的selector
第二个参数options用于指定调用切片方法的时机:

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 /// 在第一次执行之后自动移除hook Will remove the hook after the first execution.
};

它可以指定在原方法实现之后插入,这也是默认的方式,还可以使用block替换原来方法的实现,还可以在原方法之前插入指定的block,还可以只进行一次hook,第一次hook之后就会自动被移除。
第三个参数block是用于替换被hook selector的代码块
第四个参数error用于获取hook过程中的错误。

2. Hook流程分析

static id aspect_add(id self, SEL selector/*需要hook的SEL*/, AspectOptions options/*切片的时机*/, id block/*切片的执行方法*/, NSError **error) {
//.....
__block AspectIdentifier *identifier = nil;
//aspect_performLocked是一个自旋锁。自旋锁是效率比较高的一种锁,相比@synchronized来说效率高得多。
aspect_performLocked(^{
//是否允许hook
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
//添加一个aspect__selectName === AspectsContainer
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//将调用数据封装成AspectIdentifier
//得到了aspectContainer之后,就可以开始准备我们要hook方法的一些信息。这些信息都装在AspectIdentifier中,所以我们需要新建一个AspectIdentifier。
//这个instancetype方法,只有一种情况会创建失败,那就是aspect_isCompatibleBlockSignature方法返回NO。返回NO就意味着我们要替换的方法block和要替换的原方法,两者的方法签名是不相符的
//方法签名匹配成功之后,就会创建好一个AspectIdentifier。
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
//容器中添加identifier
//aspectContainer容器会把它加入到容器中。完成了容器和AspectIdentifier初始化之后,就可以开始准备进行hook了。通过options选项分别添加到容器中的
[aspectContainer addAspect:identifier withOptions:options];
//添加一个aspects__selectName === AspectsContainer(identifier, options) ==== AspectIdentifier(包含了调用所需要的所有信息)
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}

Hook的最开始会调用aspect_add,这里会先用一个自旋锁来保证线程的安全,自旋锁是效率比较高的一种锁,相比@synchronized来说效率高得多,紧接着会调用aspect_isSelectorAllowedAndTrack来判断是否允许hook。如果允许hook那么会使用aspect__selectName作为属性名创建一个AspectsContainer类型的关联属性,这里需要注意的是每hook一个selector就会在当前对象中多出一个对应的Aspects容器,这里面存放的都是针对一个selector的切片,因为一个selecor可能会有多个不同的切片,每种类型的AspectOptions作为一个AspectIdentifier存放在AspectsContainer容器中。
最后通过aspect_prepareClassAndHookSelector对方法进行Hook.在介绍aspect_prepareClassAndHookSelector之前我们看下上面遗留的一些问题:

  1. 如何判断是否允许Hook
  2. aspect_getContainerForObject 的逻辑,AspectsContainer结构
  3. AspectIdentifier 结构

判断是否允许Hook

static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
//只用初始化一次的结构
static NSSet *disallowedSelectorList;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
//不允许hook的方法selector
disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
});

// Check against the blacklist.
NSString *selectorName = NSStringFromSelector(selector);
//如果当前的selectorName在disallowedSelectorList中不允许hook抛出错误
if ([disallowedSelectorList containsObject:selectorName]) {
NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
AspectError(AspectErrorSelectorBlacklisted, errorDescription);
return NO;
}

// Additional checks.
AspectOptions position = options & AspectPositionFilter;
//不允许在dealloc之后 或者替换dealloc
if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
return NO;
}

//在指定的类中找不到需要hook的方法(如果self和self.class里面都找不到该selector,会报错找不到该方法)
if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
return NO;
}

// Search for the current class and the class hierarchy IF we are modifying a class object
if (class_isMetaClass(object_getClass(self))) {

Class klass = [self class];
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
Class currentClass = [self class];

AspectTracker *tracker = swizzledClassesDict[currentClass];
//是否在子类已经hook过了
if ([tracker subclassHasHookedSelectorName:selectorName]) {
NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}

do {
//获取当前类的tracker
tracker = swizzledClassesDict[currentClass];
//查看是否有hook了当前selector
if ([tracker.selectorNames containsObject:selectorName]) {
if (klass == currentClass) {
// Already modified and topmost!
return YES;
}
//在当前类中已经hook过
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}
} while ((currentClass = class_getSuperclass(currentClass)));
//经过上面合法性hook判断和类方法不允许重复替换的检查后,到此,就可以把要hook的信息记录下来,用AspectTracker标记
//当前类tracker -- 待hook方法
// 父tracker -- 待hook方法
// 父tracker -- 待hook方法
currentClass = klass;
AspectTracker *subclassTracker = nil;
do {
//当前类的tracker
tracker = swizzledClassesDict[currentClass];
//如果没有的话则新建tracker
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}

//非第一次则添加到subclassTracker
if (subclassTracker) {
[tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
} else {
//第一次添加到tracker
[tracker.selectorNames addObject:selectorName];
}
// All superclasses get marked as having a subclass that is modified.
subclassTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
} else {
return YES;
}
return YES;
}

aspect_isSelectorAllowedAndTrack方法中主要做两件事情:

  1. 判断当前selector是否允许Hook
  2. 如果还没建立Tracker则建立Tracker的层级关系

首先会检查当前需要hook的selector是否在黑名单中,Aspect里面不允许hook “retain”, “release”, “autorelease”, “forwardInvocation:”4种方法,而且hook “dealloc”方法的时机必须是before,并且selector要能被找到,并且在对象继承层级上没有被hook过,如果这些条件都满足才允许Hook。

然后重建Tracker的层级关系,每个AspectTracker包含被跟踪的类以及这个类被hook替换的selector名称,以及它子类的tracker。

aspect_getContainerForObject 的逻辑

static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
NSCParameterAssert(self);
SEL aliasSelector = aspect_aliasForSelector(selector);
//设置关联对象aspect__selectorName AspectsContainer
//用这个字符串标记所有的selector,都加上前缀"aspects"。然后获得其对应的AssociatedObject关联对象,如果获取不到,就创建一个关联对象。最终得到selector有"aspects"前缀,对应的aspectContainer。
AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
if (!aspectContainer) {
aspectContainer = [AspectsContainer new];
objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
}
return aspectContainer;
}

aspect_getContainerForObject 方法中会在当前对象中为每个hook的selector添加一个关联属性,属性名称为aspect__selectorName 类型为AspectsContainer,AspectsContainer里面只存储一个selector相关的所有hook信息。

AspectsContainer 和 AspectIdentifier的结构

上面介绍过了AspectsContainer 代表的是一个selector 所包含的全部hook方法,包含在原有selector之前,之后,替换原有selector的block。
所以AspectsContainer 包含三个数组 beforeAspects,insteadAspects,afterAspects:

@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

在添加的时候会根据插入的位置分别存储到不同的数组:

- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
NSParameterAssert(aspect);
NSUInteger position = options&AspectPositionFilter;
switch (position) {
//会往NSArray后面添加aspect后返回新的数组的地址
case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break;
}
}

AspectIdentifier 代表一个Aspect的切片的具体内容。里面包含了单个的 Aspect 的具体信息,包括执行时机,要执行 block 所需要用到的具体信息:包括方法签名、参数等等。

@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

我们看下它的初始化方法:

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {

//这个aspect_blockMethodSignature的目的是把传递进来的AspectBlock转换成NSMethodSignature的方法签名。
NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error);
//这个函数的作用是把我们要替换的方法block和要替换的原方法,通过签名进行对比,如果不兼容则返回nil
if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
return nil;
}
AspectIdentifier *identifier = nil;
if (blockSignature) {
identifier = [AspectIdentifier new];
identifier.selector = selector;//selector
identifier.block = block; //用于替换的block
identifier.blockSignature = blockSignature;//block签名
identifier.options = options; //hook位置
identifier.object = object; // 被hook的对象
}
return identifier;
}

aspect_blockMethodSignature 用于获取block的签名信息,大家可以看下下面的注释,关于如何从Block中获取签名可以查看Aspects框架中Block的使用这篇博客。


static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
AspectBlockRef layout = (__bridge void *)block;
//把入参block强制转换成AspectBlockRef类型,然后判断是否有AspectBlockFlagsHasSignature的标志位,如果没有,报不包含方法签名的error。
if (!(layout->flags & AspectBlockFlagsHasSignature)) {
//block 不包含方法签名
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
desc += 2 * sizeof(void *);
}
//desc就是原来block里面对应的descriptor指针。descriptor指针往下偏移2个unsigned long int的位置就指向了copy函数的地址,
//如果包含Copy和Dispose函数,那么继续往下偏移2个(void)的大小。
//这时指针肯定移动到了const char signature的位置。如果desc不存在,那么也会报错,该block不包含方法签名。
if (!desc) {
//block 不包含方法签名
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
const char *signature = (*(const char **)desc);
//到了这里,就保证有方法签名且存在。最后调用NSMethodSignature的signatureWithObjCTypes方法,返回方法签名。
return [NSMethodSignature signatureWithObjCTypes:signature];
}

拿到Block签名后就可以通过aspect_isCompatibleBlockSignature来比较block的签名和要hook的selector方法的签名是否兼容:

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {

//....

BOOL signaturesMatch = YES;
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
//先比较方法签名的参数个数是否相等
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
//比较我们要替换的方法里面第一个参数是不是_cmd,对应的Type就是@
if (blockSignature.numberOfArguments > 1) {
const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
if (blockType[0] != '@') {
signaturesMatch = NO;
}
}
// Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
// The block can have less arguments than the method, that's ok.
// methodSignature 和 blockSignature 的return value都是void,所以对应的都是v。
// methodSignature的argument 0 是隐含参数 self,所以对应的是@。blockSignature的argument 0 是block,所以对应的是@?。
// methodSignature的argument 1 是隐含参数 _cmd,所以对应的是:。blockSignature的argument 1 是,所以对应的是@""。
// 从argument 2开始才是方法签名后面的对应可能出现差异,需要比较的参数列表。
if (signaturesMatch) {
for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
// Only compare parameter, not the optional type data.
if (!methodType || !blockType || methodType[0] != blockType[0]) {
signaturesMatch = NO; break;
}
}
}
}
if (!signaturesMatch) {
NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
AspectError(AspectErrorIncompatibleBlockSignature, description);
return NO;
}
return YES;
}

aspect_isCompatibleBlockSignature首先会判断block和selector的参数个数是否一致,然后检查block的第一个参数是否是_cmd,如果这两个条件都满足后才会继续从第二个参数开始比较block和selector之间的参数是否匹配。

invokeWithInfo 是调度切片block的方法,这个留在后面介绍。

到目前位置我们做了如下工作:在hook一个selector的时候会先判断当前selector是否可以被hook,如果可以则进行下一步:
紧接着我们会使用关联属性来为当前hook方法添加一个AspectsContainer,AspectsContainer当中有三个关键数组,分别用于存放option类型为AspectPositionAfter,AspectPositionInstead,AspectPositionBefore 的 AspectIdentifier。每个AspectIdentifier都包含一个切片的全部信息,这里最关键的是在初始化的时候提取的block签名信息,并且在这里会使用block签名信息和selector签名信息进行兼容性检查。AspectIdentifier内部还包含触发对应block的方法。

接下来我们继续看下关键的Hook部分:

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
//创建一个以"_Aspects_"为结尾的class作为当前的class的子类
Class klass = aspect_hookClass(self, error);
//获取本意要执行的selector
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);

//这里是判断当前IMP是不是_objc_msgForward或者_objc_msgForward_stret,即判断当前IMP是不是消息转发。如果是消息转发的话就不做hook处理
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
//如果不是消息转发,就先获取当前原始的selector对应的IMP的方法编码typeEncoding。
const char *typeEncoding = method_getTypeEncoding(targetMethod);
//获取aspect_selector方法
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
//如果子类里面不能响应aspects_xxxx,就为klass添加aspects_xxxx方法,方法的实现为原生方法的实现。也就是将原始的方法添加到创建的以"_Aspects_"为结尾的class
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
}
//将slector指向_objc_msgForward 和_objc_msgForward_stret,可想而知,当selector被执行的时候,也会触发消息转发从而进入forwardInvocation,而我们又对forwardInvacation进行了swizzling,因此,最终转入我们自己的处理逻辑代码中。
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}

aspect_prepareClassAndHookSelector方法中会先创建一个以_Aspects_为结尾的class作为当前的class的子类,如果当前方法不是消息分发方法_objc_msgForward或者_objc_msgForward_stret,就进行方法替换。比如我们要调用classA 的testA方法,并且testA已经被hook了,那么这时候我们会创建一个classA_Aspects_类作为classA的子类。在调用classA 的testA方法的时候会先看下classA_Aspects_是否有aspects_testA方法,如果没有则添加一个方法aspects_testA指向原来的testA,并且将classA_Aspects_的selector方法替换为aspect_getMsgForwardIMP(self, selector)走消息分发途径。消息具体怎么分发的我们看下aspect_hookClass

static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class; //是获取类对象
Class baseClass = object_getClass(self); //是获取到类的isa。
NSString *className = NSStringFromClass(baseClass);//类名

if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
//如果包含了@"_Aspects_"后缀,代表该类已经被hook过了,直接return。
//如果不包含@"_Aspects_"后缀,再判断是否是baseClass是否是元类,如果是元类,调用aspect_swizzleClassInPlace。
} else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
//如果也不是元类,再判断statedClass 和 baseClass是否相等,如果不相等,说明为KVO过的对象,因为KVO的对象isa指针会指向一个中间类。对KVO中间类调用aspect_swizzleClassInPlace。
} else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}

//当className没有包含@"_Aspects_"后缀,并且也不是元类,也不是KVO的中间类
//在当前类名中添加AspectsSubclassSuffix
//hook 是在runtime中动态创建子类的基础上实现的
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
//正常情况下是没有这个添加AspectsSubclassSuffix的类的
if (subclass == nil) {
//创建一个新的class,它的父类为baseClass
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);
//把class的实例方法替换成返回statedClass,也就是说把调用class时候的isa指向了statedClass了。
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
//将当前实例对象的class替换为添加AspectsSubclassSuffix的类
//所有的 swizzling 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类,
//也就是,当你在 remove aspects 的时候,如果发现当前对象的 aspect 都被移除了,那么,你可以将 isa 指针重新指回对象本身的类,从而消除了该对象的 swizzling ,
//同时也不会影响到其他该类的不同对象)这样对原来替换的类或者对象没有任何影响而且可以在子类基础上新增或者删除aspect。
object_setClass(self, subclass);
//hookClass阶段就完成了,成功的把self hook成了其子类 xxx_Aspects_
return subclass;
}

aspect_hookClass 方法中会通过objc_allocateClassPair来创建一个“类名+_Aspects_”结尾的新类作为baseClass的子类。并且通过aspect_swizzleForwardInvocation将当前类forwardInvocation方法的实现为__ASPECTS_ARE_BEING_CALLED__。然后将当前实例对象的class替换为添加AspectsSubclassSuffix的类,所有的 swizzling 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类,也就是,当你在remove aspects 的时候,如果发现当前对象的 aspect 都被移除了,那么我们可以将 isa 指针重新指回对象本身的类,从而消除了该对象的 swizzling ,同时也不会影响到其他该类的不同对象,这样对原来替换的类或者对象没有任何影响而且可以在子类基础上新增或者删除aspect。

我们看下消息转发方法的替换:

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
NSCParameterAssert(klass);
//主要作用是替换当前类forwardInvocation方法的实现为__ASPECTS_ARE_BEING_CALLED__
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
//当调用forwardInvocation进行消息分发的时候会调用__ASPECTS_ARE_BEING_CALLED__ ,__ASPECTS_ARE_BEING_CALLED__ 的selector为 __aspects_forwardInvocation
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
}

如果klass没有实现forwardInvocation就会添加forwardInvocation方法并指向__ASPECTS_ARE_BEING_CALLED__实现,这种情况originalImplementation返回的是nil,所以不会走下面的流程,也就是说如果原来方法没有forwardInvocation,则会添加forwardInvocation方法,如果有的话将会替换forwardInvocation的实现为__ASPECTS_ARE_BEING_CALLED__,并新建一个方法__aspects_forwardInvocation指向原先已经实现的forwardInvocation,也就是说经过aspect_swizzleForwardInvocation处理后,klass中一定有一个forwardInvocation方法指向__ASPECTS_ARE_BEING_CALLED__,如果原来有的情况下还会多出一个__aspects_forwardInvocation指向原来的forwardInvocation。

我们再来看下****ASPECTS_ARE_BEING_CALLED****

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {

//获取原始的selector
SEL originalSelector = invocation.selector;
//获取带有aspects_xxxx前缀的方法
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);

//将当前请求selector替换为aspects_xxxx
//比如调用sel1--->aspects_sel1--->AspectContainer
invocation.selector = aliasSelector;
//获取实例对象的容器objectContainer,这里是之前aspect_add关联过的对象。
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
//获取获得类对象容器classContainer
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
//初始化AspectInfo,传入self、invocation参数
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);

//如果hook没有被正常执行,那么就应该执行原来的方法。
// 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)];
}

首先进入这里的时候会通过关联属性来获取aspect_selector对应的属性值,如果有被hook的话则会获取到存放hook切片信息的切片容器AspectsContainer。我们使用invocation信息创建AspectInfo,
然后调用aspect_invoke宏来执行AspectsContainer中的beforeAspects,insteadAspects,以及afterAspects,如果insteadAspects为空的话,则直接执行子类的aliasSelector。我们之前已经提到过了,在aspect_prepareClassAndHookSelector方法中会在子类添加一个aspect_selector的方法它指向selector实现,所以这里会执行原来方法中的selector。如果respondsToAlias = NO 表示instancesRespondToSelector返回NO,表示调用了一个没有实现的方法,所以走原来类的消息转发机制。

我们最后再来看下aspect_invoke这个宏:

#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}

这里会遍历aspects中的AspectIdentifier,并调用invokeWithInfo执行对应的block。如果切片类型为AspectOptionAutomaticRemoval,则触发后将它移除。

- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
//block的调度者封装
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
//原始调度者的封装
NSInvocation *originalInvocation = info.originalInvocation;
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;

// Be extra paranoid. We already check that on hook registration.
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}

// The `self` of the block will be the AspectInfo. Optional.
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}

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中取出参数,赋值到argBuf中,然后再赋值到blockInvocation里
[originalInvocation getArgument:argBuf atIndex:idx];
[blockInvocation setArgument:argBuf atIndex:idx];
}
//最后把self.block赋值给blockInvocation的Target
[blockInvocation invokeWithTarget:self.block];

if (argBuf != NULL) {
free(argBuf);
}
return YES;
}

invokeWithInfo 方法中主要将原先selector的方法参数,提取出来,传到block上,再通过invokeWithTarget执行block。

总结:

最后我们来做个总结:
在我们hook某个方法的时候会在当前类上添加一个名字为aspect_selector的关联属性,这个关联属性类型为AspectContainer它是用于存放该selector相关的切片的容器,容器里面有三个数组,分别存放着
不同AspectOptions的AspectIdentifier,每个AspectIdentifier都包含着一个切片的具体信息。紧接着会创建一个以_Aspects_结尾的类作为当前类的子类,这里类里面会添加一个方法名称为aspect_selector的方法,方法的实现指向原来的selector实现。并且会为该子类添加一个指向__ASPECTS_ARE_BEING_CALLED__的forwardInvocation方法。如果原来的类已经实现了forwardInvocation,就会添加一个__aspects_forwardInvocation方法指向原先已经实现的forwardInvocation。这个方法会在Aspect消息转发机制走不通的情况下走这里的消息转发。

这以后再将子类的selector方法调用替换为Aspect消息转发,这样我们调用这个selector的时候就会通过Aspect 消息转发进入__ASPECTS_ARE_BEING_CALLED__,在__ASPECTS_ARE_BEING_CALLED__中会先尝试取出之前的关联属性的切片容器,执行容器内的切片block,在这会如果instead切片为空的时候会调用aspect_selector,由于上面已经将aspect_selector指向原来的selector实现,所以这里会有机会调用原来类的selector方法。如果原来类不能响应selector方法,那么就走原来类的forwardInvocation,进行消息转发。

重要的类及关系介绍:

****Hook流程图:****:

Contents
  1. 1. 开源代码信息
  2. 2. 源码解析
  3. 3. 总结: