在 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的实现。
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 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中。
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; } 复制代码
首先获取被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; } 复制代码
// 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的指向。
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
中移除类名字符串的记录。