iOS开发中的AOP利器 - Aspects 源码分析(二)

栏目: Java · 发布时间: 6年前

内容简介:在通过这个函数首先把传进来的

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 指向调用方法的原 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 事件)

blockInvocation 设置参数解析

  • 设置了 block 的第一个位置的参数为 AspectInfo * info , 这样做及未来方便内部遍历设置参数 (与selector保持一致,自定义参数从 索引为2的位置开始),又方便了外界在定义hook的事件是获取到实例对象 - [info instance]

  • getArgument:atIndex: 返回的是对应索引参数的指针(地址)。假如参数是一个对象指针的话,会返回对象的指针地址。而 setArgument:atIndex: 会把传进来的参数(指针)拷贝其指向的内容到相应的索引位置中。所以 argBuf 在整个 for 循环中可以不断地使用同一个指针并不断的 reallocf 返回指向一定堆空间的指针。 argBuf 指针只是作为一个设置参数的中介,每一个 for 循环后 setArgument :atIndex: 都会把 argBuf 指向的内容拷贝到invocation中。

hook的移除

AspectIdentifierremove 方法,会调用到下面的函数

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 中移除类名字符串的记录。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First HTML and CSS

Head First HTML and CSS

Elisabeth Robson、Eric Freeman / O'Reilly Media / 2012-9-8 / USD 39.99

Tired of reading HTML books that only make sense after you're an expert? Then it's about time you picked up Head First HTML and really learned HTML. You want to learn HTML so you can finally create th......一起来看看 《Head First HTML and CSS》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具