iOS开发中的AOP利器 - Aspects 源码分析(二
执行hook事件
在Aspects
源码分析的第一篇文章中主要分析了为hook
做的准备工作,接下来分析一下,当 selector
执行时是如何执行你自己添加的自定义hook
事件的。
通过hook
准备工作的处理后 ,外界调用的hook selector
会直接进入消息转发执行到方法forwardInvocation:
,然后此时forwardInvocation:
方法的IMP是指向处理hook
的函数 __ASPECTS_ARE_BEING_CALLED__
,这个函数也是整个hook
事件的核心函数。代码实现如下
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]; //aliasSelector 已经在 aspect_prepareClassAndHookSelector 函数中替换为原来selector的实现 , 这里就是调回原方法的实现代码
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)];
}
这个函数首先把传进来的NSInvocation
对象的selector
赋值为 IMP
指向调用方法的原IMP
的aliasSelector
, 这样可以方便调用会原方法的IMP的实现。
获取hook事件容器
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
这里是通过aliasSelector分别取出绑定在 hook对象 以及 hook class (hook对象的isa指针指向的Class)中对应的容器对象AspectsContainer
, 并生成一个 AspectInfo
对象,用于封装执行方法及hook事件是所需的实参。接下来分别是遍历两个容器对象中的三个数组(beforeAspects 、insteadAspects 、afterAspects
)是否有 hook
的标识对象AspectIdentifier
, 如果有的话就执行相应的hook
事件。insteadAspects
如果这个数组有对象存放,就说明原方法的实现被替换为执行 insteadAspects
里的hook
事件了。
hook执行
//执行hook
aspect_invoke(classContainer.beforeAspects, info);
//hook执行的宏代码
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
//根据block得签名字符串 , 生成对应的消息调用对象。用来在设置完参数后调用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.
//这里设置Block的 第一个参数为传进来的AspectInfo对象 , 第0位置的参数是Block本身
if (numberOfArguments > 1) { //有参数的话就吧第一个参数 设置为 AspectInfo , 第0位置是block本身。
/**
官方文档解析 : When the argument value is an object, pass a pointer to the variable (or memory) from which the object should be copied
&info : info对象指针的地址
这样传参的目的是保证了,参数无论是普通类型参数还是对象都可以通过你传进来的指针,通过拷贝指针指向的内容来获取到 普通类型数据 或者 对象指针。
*/
[blockInvocation setArgument:&info atIndex:1];
}
void *argBuf = NULL;
//遍历参数类型typeStr , 为blockInvocation对应的参数创建所需空间 , 赋值数据 , 设置blockInvocation参数
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize; //实参多需要的空间大小
NSGetSizeAndAlignment(type, &argSize, NULL); //根据encodeType 字符串 创建对应空间存放block的参数数据所属要的size
if (!(argBuf = reallocf(argBuf, argSize))) { //创建size大小的空间
AspectLogError(@"Failed to allocate memory for block invocation.");
return NO;
}
[originalInvocation getArgument:argBuf atIndex:idx]; //获取到指向对应参数的指针
[blockInvocation setArgument:argBuf atIndex:idx]; //把指向对应实参指针的地址(相当于指向实参指针的指针)传给invocation 进行拷贝,得到的就是指向实参对象的指针
}
[blockInvocation invokeWithTarget:self.block]; //设置完实参执行block
if (argBuf != NULL) {
free(argBuf); //c语言的创建空间 ,用完后需要释放,关于c语言的动态内存相关资料可以看 https://blog.csdn.net/qq_29924041/article/details/54897204
}
return YES;
}
可以看出 AspectIdentifier
的-invokeWithInfo
是执行hook
事件最终的方法。该方法主要处理的事情是:根据传进来的AspectInfo
对象为最初定义hook
事件的Block
设置相应的参数。并执行Block(hook
事件)
blockInvocation
设置参数解析
-
设置了
block
的第一个位置的参数为AspectInfo * info
, 这样做及未来方便内部遍历设置参数 (与selector保持一致,自定义参数从 索引为2的位置开始),又方便了外界在定义hook的事件是获取到实例对象 -[info instance]
-
getArgument:atIndex:
返回的是对应索引参数的指针(地址)。假如参数是一个对象指针的话,会返回对象的指针地址。而setArgument:atIndex:
会把传进来的参数(指针)拷贝其指向的内容到相应的索引位置中。所以argBuf
在整个for
循环中可以不断地使用同一个指针并不断的reallocf
返回指向一定堆空间的指针。argBuf
指针只是作为一个设置参数的中介,每一个for
循环后setArgument :atIndex:
都会把argBuf
指向的内容拷贝到invocation中。
hook的移除
AspectIdentifier
的remove
方法,会调用到下面的函数
static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) {
NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type.");
__block BOOL success = NO;
aspect_performLocked(^{
id self = aspect.object; // strongify
if (self) {
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
success = [aspectContainer removeAspect:aspect]; //重container的 三个数组中移除aspect
aspect_cleanupHookedClassAndSelector(self, aspect.selector);
// destroy token
aspect.object = nil;
aspect.block = nil;
aspect.selector = NULL;
}else {
NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
}
});
return success;
}
1. 移除AspectContainer中的AspectIdentifier
首先获取被hook的对象中通过runtime
绑定的关联属性 ---AspectsContainer *aspectContainer
,并分别移除数组的hook标识对象 - AsepctIdentifier * aspect
,实现代码如下:
//AspectContainer的实例方法
- (BOOL)removeAspect:(id)aspect {
for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
NSStringFromSelector(@selector(insteadAspects)),
NSStringFromSelector(@selector(afterAspects))]) {
NSArray *array = [self valueForKey:aspectArrayName];
NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
if (array && index != NSNotFound) {
NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
[newArray removeObjectAtIndex:index];
[self setValue:newArray forKey:aspectArrayName];
return YES;
}
}
return NO;
}
2.还原selector指向的IMP
// Check if the method is marked as forwarded and undo that.
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (aspect_isMsgForwardIMP(targetMethodIMP)) {
// Restore the original method implementation.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
IMP originalIMP = method_getImplementation(originalMethod);
NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
class_replaceMethod(klass, selector, originalIMP, typeEncoding);
AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
在进行hook
准备工作室,把selector
的IMP修改成立进入消息转发的,并且添加了一个新的selector
(asepct__selector
)指向原selector
的IMP
这里是还原selector的指向。
3.移除AspectTracker对应的记录
static void aspect_deregisterTrackedSelector(id self, SEL selector) {
if (!class_isMetaClass(object_getClass(self))) return;
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
NSString *selectorName = NSStringFromSelector(selector);
Class currentClass = [self class];
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (tracker) {
[tracker.selectorNames removeObject:selectorName];
if (tracker.selectorNames.count == 0) {
[swizzledClassesDict removeObjectForKey:tracker];
}
}
}while ((currentClass = class_getSuperclass(currentClass)));
}
如果被hook
的是类(调用的是类方法添加hook
)。在全局对象(NSMutableDictionary *swizzledClassesDict)中移除hook class
的整个向上继承关系链上的AspectTracker
中的selectors
数组中的selectorName
字符串。
4.还原被hook的实例对象的isa的指向 + 还原被hook Class的forwardInvocation:方法的IMP指向
// Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
AspectsContainer *container = aspect_getContainerForObject(self, selector);
if (!container.hasAspects) {
// Destroy the container
aspect_destroyContainerForObject(self, selector);
// Figure out how the class was modified to undo the changes.
NSString *className = NSStringFromClass(klass);
if ([className hasSuffix:AspectsSubclassSuffix]) {
Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
NSCAssert(originalClass != nil, @"Original class must exist");
object_setClass(self, originalClass); //把hook的类对象isa 从_Aspects_class -> 原来的类
AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
// We can only dispose the class pair if we can ensure that no instances exist using our subclass.
// Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
//objc_disposeClassPair(object.class);
}else {
// Class is most likely swizzled in place. Undo that.
if (isMetaClass) {
aspect_undoSwizzleClassInPlace((Class)self);
}
}
}
这里首先判断hook
对象的AspectContainer
属性数组中是否还有 AspectIndetafier
对象(hook标识)。如果没有的话就清除掉该对象的关联属性容器。接下来分两种情况进行还原处理
情况1. 被hook的是普通实例对象 : 此时需要把对象的isa指向还原会为原来的Class --> object_setClass(self, originalClass);
情况2. 被hook的是类: 还原forwardInvocation:
方法的IMP指向为原来的进入消息转发的IMP,并且从全局变量swizzledClasses
中移除类名字符串的记录。