iOS開發中的AOP利器 - Aspects 原始碼分析(二)
在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
中移除類名字串的記錄。