转载

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 指向调用方法的原 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 中移除类名字符串的记录。

原文  https://juejin.im/post/5b9b75bb6fb9a05ce7514ce1
正文到此结束
Loading...