iOS開發中的AOP利器 - Aspects 源碼分析(二)

執行hook事件

Aspects源碼分析的第一篇文章中主要分析了爲hook作的準備工做,接下來分析一下,當 selector執行時是如何執行你本身添加的自定義hook事件的。數組

經過hook準備工做的處理後 ,外界調用的hook selector 會直接進入消息轉發執行到方法forwardInvocation: ,而後此時forwardInvocation:方法的IMP是指向處理hook的函數 __ASPECTS_ARE_BEING_CALLED__,這個函數也是整個hook事件的核心函數。代碼實現以下bash

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指向調用方法的原IMPaliasSelector , 這樣能夠方便調用會原方法的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事件)post

blockInvocation設置參數解析ui

  • 設置了block的第一個位置的參數爲AspectInfo * info , 這樣作及將來方便內部遍歷設置參數 (與selector保持一致,自定義參數從 索引爲2的位置開始),又方便了外界在定義hook的事件是獲取到實例對象 - [info instance]this

  • getArgument:atIndex:返回的是對應索引參數的指針(地址)。假如參數是一個對象指針的話,會返回對象的指針地址。而setArgument:atIndex:會把傳進來的參數(指針)拷貝其指向的內容到相應的索引位置中。因此argBuf在整個for循環中能夠不斷地使用同一個指針並不斷的reallocf返回指向必定堆空間的指針。argBuf指針只是做爲一個設置參數的中介,每個for循環後setArgument :atIndex:都會把argBuf指向的內容拷貝到invocation中。spa

hook的移除

AspectIdentifierremove方法,會調用到下面的函數.net

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修改爲立進入消息轉發的,而且添加了一個新的selectorasepct__selector)指向原selectorIMP這裏是還原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中移除類名字符串的記錄。

相關文章
相關標籤/搜索