阅读 282

aspects探究

前言

aspects 是ios中一款hook框架,使用方便,功能强大,是AOP编程中不可或缺的框架

其巧妙地利用Method-swizzle实现了以block的方式来hook指定函数,除了可以hook指定对象或类,还可以指定hook的方式

Aspects

Aspect新建的核心类主要关系如下所示,Aspects 作为 NSObject 的分类,协调调用其他的类以实现hook功能

image.png

使用简介

其分别提供了类方法和对象方法来 hook 对应的对象的实例方法或者类的实例方法(hook类方法就是hook对象元类的实例方法),hook之后会返回AspectTokenAspectToken对象可以用于在合适的位置移除hook

下面是声明以及使用的案例方法,看着很简洁

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

//Aspect的使用,
//hook 实例对象的实例方法(仅仅hook该对象,不影响该实例对象所在类的其他实例对象)
id<AspectToken> aspect = [vc aspect_hookSelector:@selector(viewDidLoad:) 
    withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){
} error:nil];
//hook 类的实例方法(即该类所有的对象都被hook)
id<AspectToken> aspect = [UIViewController aspect_hookSelector:@selector(viewDidLoad:) 
    withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){
} error:nil];
//hook 元类的实例方法(即该类的类方法被hook)
id<AspectToken> aspect = [objc_getMetaClass("Person") aspect_hookSelector:@selector(logInfo) 
    withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo){
        NSLog(@"ViewController哈哈哈");
} error:nil];

//移除hook代码
[aspect remove];
复制代码

其中options枚举值关系着 hook方法执行的时机和方式,如下所示

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    //在的hook方法之后执行
    AspectPositionAfter   = 0,            /// Called after the original implementation (default)
    //替换掉hook的方法
    AspectPositionInstead = 1,            /// Will replace the original implementation.
    //在的hook方法之前执行
    AspectPositionBefore  = 2,            /// Called before the original implementation.
    //hook之后,执行一次自动移除,可以通过按位与的方式加入此功能 
    //AspectPositionAfter | AspectOptionAutomaticRemoval
    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.

};
复制代码

实现源码介绍

hook调用的入口方法就是 aspect_hookSelector,我们进入窥探究竟,此过程可以观察block是怎样一步一步被保存,最后在哪里调用的

执行外层hook方法,最后均会调用 aspect_add函数

aspect_add

此方法是hook初始化的核心实现方法,主要逻辑都在这里

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error {
    return aspect_add(self, selector, options, block, error);
}

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    NSCParameterAssert(block);
    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        //检查此次hook是否允许,且此过程引入了tracker、以便于后续校验等操作
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            //获取此类的Container,避免重复创建集合,用于保存方法签名等信息类AspectIdentifier对象
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            //创建AspectIdentifier用于保存方法签名等信息
            identifier = [AspectIdentifier identifierWithSelector:selector 
                object:self options:options block:block error:error];
            if (identifier) {
                //将签名信息放到集合Container中,以便于查找重复和调用
                //Container与被绑的self关联,self为对象或者类对象
                [aspectContainer addAspect:identifier withOptions:options];

                // Modify the class to allow message interception.
                //hook方法,实现block与原方法的imp逻辑处理(不是单纯的交换)
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}
复制代码

可以看到上面的逻辑:

1、校验方法并引入tracker,用于更新后续校验等操作

2、一个类或者对象统一使用container,来管理AspectIdentifier方法签名等信息

3、根据block等创建方法签名等信息到AspectIdentifier类中

4、添加AspectIdentifier到container中

5、hook逻辑实现(实际交换利用了消息转发来处理回调)

而hook的逻辑则是在交换后的方法 __ASPECTS_ARE_BEING_CALLED__ 中处理,后续会介绍

aspect_isSelectorAllowedAndTrack

该方法是检查hook,且此过程引入了tracker,避免了重复的hook和其他操作

static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, 
    AspectOptions options, NSError **error) {
    //不允许交换的单例黑名单,交换后会出现内存问题
    static NSSet *disallowedSelectorList;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        disallowedSelectorList = [NSSet 
            setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
    });

    // Check against the blacklist.
    //检查交换方法是否在黑名单中
    NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }
    //如果交换的是dealloc方法
    // Additional checks.
    AspectOptions position = options&AspectPositionFilter;
    if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
        NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }
    //查看该类中是能响应此方法
    if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        return NO;
    }

    // Search for the current class and the class hierarchy IF we are modifying a class object
    //当前对象是否是类对象,即是否调用的类方法,或者类对象的实例方法(所有的实例对象都会调用),此情况不能重复hook
    if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        //单例字段,用于以class为键值,保存该类的tracker信息
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];
        
        //获取tracker信息
        AspectTracker *tracker = swizzledClassesDict[currentClass];
        //如果子类已经有hook后的tracker信息,子类hook则不能在hook父类,不然会出现问题
        if ([tracker subclassHasHookedSelectorName:selectorName]) {
            //从tracker中子类查找是否已经被hook了,子类信息被保存在当前类对应的tracker中
            NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
            NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
            NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
            AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
            return NO;
        }
        //利用循环开始查找此类的实际响应者,找到后返回YES
        do {
            tracker = swizzledClassesDict[currentClass];
            if ([tracker.selectorNames containsObject:selectorName]) {
                if (klass == currentClass) {
                    // Already modified and topmost!
                    return YES;
                }
                NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
                AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
                return NO;
            }
        } while ((currentClass = class_getSuperclass(currentClass)));

        类中没找到该方法,说明该方法被修改过了
        currentClass = klass;
        AspectTracker *subclassTracker = nil;
        //遍历当前类的父类,生成tracker,保存到单例集合中
        //更新tracker的selector集合,且每个父类保存所有的子类tracker集合,以selectorName为键值保存
        do {
            tracker = swizzledClassesDict[currentClass];
            if (!tracker) {
                tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
                swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
            }
            if (subclassTracker) {
                [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
            } else {
                [tracker.selectorNames addObject:selectorName];
            }

            // All superclasses get marked as having a subclass that is modified.
            subclassTracker = tracker;
        }while ((currentClass = class_getSuperclass(currentClass)));
    } else {
        return YES;
    }
return YES;
}
复制代码

aspect_getContainerForObject

其为获取container的方法,就不多介绍了

static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    SEL aliasSelector = aspect_aliasForSelector(selector);
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    if (!aspectContainer) {
        aspectContainer = [AspectsContainer new];
        objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}
复制代码

AspectIdentifier初始化签名信息

AspectIdentifier类通过调用identifierWithSelector:selector object:self options:options block:block error:error]函数保存签名,将block保存下来,是非常重要的一点

其主要实现逻辑如下所示

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
    NSCParameterAssert(block);
    NSCParameterAssert(selector);
    //获取block的签名信息
    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error);
    //检查block的签名信息是否和hook的selector是否对应(肯定不是完全一致)
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }
    //将保存的签名一起selector等信息保存到AspectIdentifier中去
    AspectIdentifier *identifier = nil;
    if (blockSignature) {
        identifier = [AspectIdentifier new];
        identifier.selector = selector;
        identifier.block = block;
        identifier.blockSignature = blockSignature;
        identifier.options = options;
        identifier.object = object; // weak
    }
    return identifier;
}
复制代码

可以看到block的签名和校验过程是非常重要的,保存下来了,那么后面才可以在对应的地方回调,下面看看block的签名信息是怎么获得的,又如何校验签名信息的

block签名信息获取

block签名的获取是调用了aspect_blockMethodSignature方法实现

其中引入了block的内部结构,以便于从中获取block的函数签名信息

// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
        //当block有此参数时,会有void (*copy)(void *dst, const void *src);参数
	AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
        //是否有签名信息,当有签名信息的block,才能有signatrue签名等信息
	AspectBlockFlagsHasSignature          = (1 << 30)
};
typedef struct _AspectBlock {
	__unused Class isa;
	AspectBlockFlags flags;
	__unused int reserved;
	void (__unused *invoke)(struct _AspectBlock *block, ...);
	struct {
		unsigned long int reserved;
		unsigned long int size;
                //当block的枚举值为AspectBlockFlagsHasSignature时,下面会有函数签名等信息
		// requires AspectBlockFlagsHasCopyDisposeHelpers
		void (*copy)(void *dst, const void *src);
		void (*dispose)(const void *);
		// requires AspectBlockFlagsHasSignature
		const char *signature;
		const char *layout;
	} *descriptor;
	// imported variables
} *AspectBlockRef;

//获取函数签名信息
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    //将block强转成AspectBlockRef结构,可以从中获取到签名信息
    AspectBlockRef layout = (__bridge void *)block;
    //如果是AspectBlockFlagsHasSignature枚举类型,则可以获取函数签名信息,否则不能直接结束
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        NSString *description = [NSString stringWithFormat:
            @"The block %@ doesn't contain a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    //通过上面的block结构信息,可以看到参数在block的descriptor参数中
    //而descriptor参数中有两个unsigned long int类型,和一个void *类型参数,由于他们是顺序储存的
    //则可以通过过移位的方式获取第四个参数signature
    void *desc = layout->descriptor;
    desc += 2 * sizeof(unsigned long int); //计算前两个参数大小,移位
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
            //由于这个参数是AspectBlockFlagsHasCopyDisposeHelpers才会有的,所以需要判断
            desc += 2 * sizeof(void *); //计算第三个参数大小移位
    }
    if (!desc) {
        NSString *description = [NSString stringWithFormat:
            @"The block %@ doesn't has a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    //获取到了最终的签名信息,返回
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}
复制代码

block签名信息校验

前面获取到了block的签名信息,后面则需要通过签名信息和selector对应的签名信息进行对比,如果参数不一致,则不能进行hook,否则可以hook

即调用 aspect_isCompatibleBlockSignature方法进行检测签名信息,下面探究下怎么判断的

tatic BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, 
    id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

    BOOL signaturesMatch = YES;
    //获取到对象hook的selector签名信息
    NSMethodSignature *methodSignature = [[object class] 
        instanceMethodSignatureForSelector:selector];
    //对比selector和block的签名信息来判断是否一致
    //先对比签名数量,block的数量默认要比selector的少
    //select默认参数为(id self, SEL _cmd, ...),签名信息为 v@: @和:表示两个参数self和_cmd
    //block的默认参数则为(block, ...), 签名信息为v@? 其中@?是组合的表示一个
    //因此block的默认参数比selector要少一个,所以追加了一个遵循<AspectInfo>协议的参数
    //所以他们参数数量相等了
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        //block参数比selector多,不符合条件,无法替换
        signaturesMatch = NO;
    }else {
        block的参数没有selector的多,属于则开始继续校验
        if (blockSignature.numberOfArguments > 1) {
            //先检查第block的第2个参数是否是id类型毕竟,第一个看到的参数为遵循<AspectInfo>协议的对象
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            if (blockType[0] != '@') {
                signaturesMatch = NO;
            }
        }
        // The block can have less arguments than the method, that's ok.
        //从第二个参数开始比较后面的参数是否一致,否则签名不一致,算失败
        if (signaturesMatch) {
            for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
                const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
                const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
                // Only compare parameter, not the optional type data.
                if (!methodType || !blockType || methodType[0] != blockType[0]) {
                    signaturesMatch = NO; break;
                }
            }
        }
    }

    if (!signaturesMatch) {
        NSString *description = [NSString stringWithFormat:
            @"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }
    return YES;
}
复制代码

addAspect

将AspectIdentifier参数保存到container中,根据不同的类型将保存的block相关新保存到不同数组,以被交换的selector为基准,将block分别放到其之前、替换、之后,分为三个数组,一次保存

之前和之后则是不与原方法冲突,仅仅是追加方法,而替换方法,则不在调用原方法

- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
    NSParameterAssert(aspect);
    NSUInteger position = options & AspectPositionFilter;
    switch (position) {
        case AspectPositionBefore:  self.beforeAspects  = 
            [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
        case AspectPositionInstead: self.insteadAspects = 
            [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
        case AspectPositionAfter:   self.afterAspects   = 
            [(self.afterAspects  ?:@[]) arrayByAddingObject:aspect]; break;
    }
}
复制代码

实现hook来实现函数交换等逻辑

Aspects中hook不是单纯的交换信息,前面提到了,通过调用aspect_prepareClassAndHookSelector函数来实现响应功能,如果熟悉KVO和消息发送,相信会一目了然

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    //获取用于hook生成的子类,名字前缀_Aspects_,是不是和 NSKVONotifying_ 很相似
    Class klass = aspect_hookClass(self, error);
    //获取被交换的selector的实际方法,如果没有或获取到父类的Method
    Method targetMethod = class_getInstanceMethod(klass, selector);
    //获取 selector的 IMP
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    //检查该方法是否消息转发的imp -- _objc_msgForward, 如果是则不需要额外处理
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        //获取selector的方法签名等信息
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        //SEL:aspect__logName --- (IMP)logName
        SEL aliasSelector = aspect_aliasForSelector(selector);
        //检查改了实例方法是否有该犯法
        if (![klass instancesRespondToSelector:aliasSelector]) {
            //如果没有则添加该方法到该子类中,避免交换了父类的方法
            __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
            NSCAssert(addedAlias, 
                @"Original implementation for %@ is already copied to %@ on %@",
                NSStringFromSelector(selector), 
                NSStringFromSelector(aliasSelector), klass);
        }

        // We use forwardInvocation to hook in.
        //logName --- _objc_msgForward
        //将该方法和 Aspects定义的方法进行交换
        /即调用aspect_getMsgForwardIMP,将原方法调用改为_objc_msgForward,即应用消息转发
        class_replaceMethod(klass, selector, a
            spect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}
复制代码

上面的方法中提到了 aspect_hookClass方法,我们看是怎么生成hook需要的子类,并且其中做了什么,来应对imp交换为_objc_msgForward 操作

aspect_hookClass

aspect_hookClass为实现hook的方法,下面看看怎么做的

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    获取当前类的class(如果是子类可能会被重写)
    Class statedClass = self.class;
    //获取当前对象的所属类信息,如果已经是类对象,则获取到的是元类
    Class baseClass = object_getClass(self);
    
    NSString *className = NSStringFromClass(baseClass);
    
    // Already subclassed
    //检查当前类是否是已经创建hook之后的子类,如果是,直接返回子类信息即可
    if ([className hasSuffix:AspectsSubclassSuffix]) {
		return baseClass;
        
    }else if (class_isMetaClass(baseClass)) {
        //如果不是子类当前类是元类,则直接交换即可
        //不需要额外生成子类,并且标记为已经hook(被添加到swizzledClasses中去了)
        return aspect_swizzleClassInPlace((Class)self);
        // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
    }else if (statedClass != baseClass) {
        //如果当前类和所属类baseClass不一致,说明已经生成了新的子类了,直接实现子类方法交换
        return aspect_swizzleClassInPlace(baseClass);
    }
    //还没有生成子类,开始生成子类,在子类中处理信息
    //如果交换的是一个实例对象,可以避免对其他实例对象造成影响
    // Default case. Create dynamic subclass.
    //生成子类的名字,一般父类名字前加上固定前缀
    const char *subclassName = [className 
        stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        //根据当前类创建子类
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        if (subclass == nil) {
            //创建失败,则已经不需要处理了,后面都没发处理了,失败
            NSString *errrorDesc = [NSString stringWithFormat:
                @"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }
        //前面将原调用指针改成了 ,则一定会走消息转发流程
        //这里将消息转发的forwardInvocation:方法替换成新的消息转发名字
        aspect_swizzleForwardInvocation(subclass);
        //分别重写该类的class的实例方法和类方法直,均直接返回父类的class
        aspect_hookedGetClass(subclass, statedClass);
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        //注册该子类
        objc_registerClassPair(subclass);
    }
    //将当前类指向为新生成的子类
    object_setClass(self, subclass);
    return subclass;
}
复制代码

aspect_hookedGetClass

aspect_hookedGetClass为普通的重写class的方法,如下所示

static void aspect_hookedGetClass(Class class, Class statedClass) {
    NSCParameterAssert(class);
    NSCParameterAssert(statedClass);
	Method method = class_getInstanceMethod(class, @selector(class));
        //生成新的imp返回老的class信息
	IMP newIMP = imp_implementationWithBlock(^(id self) {
		return statedClass;
	});
        //将子类的class实现改为新生成的
	class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
}
复制代码

aspect_swizzleForwardInvocation

aspect_swizzleForwardInvocation为交换消息转发的方法,并生成一个新的方法,如下所示

其中__aspects_forwardInvocation被作为备用的消息转发方法

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";

static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    //将子类的消息转发的方法 forwardInvocation: 替换为__ASPECTS_ARE_BEING_CALLED__实现
    //后面的hook后的方法实现均在__ASPECTS_ARE_BEING_CALLED__函数中
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), 
        (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    if (originalImplementation) {
        //替换完成之后,添加新生成的__aspects_forwardInvocation方法,实现和消息转发的一样
        //留着作为备用消息转发方法功能
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), 
            originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
复制代码

ASPECTS_ARE_BEING_CALLED

__ASPECTS_ARE_BEING_CALLED__为消息转发是调用的回调方法(实际上就是调用hook后的方法,原方法的imp都被替换成了_objc_msgForward,即均会执行消息转发功能)

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;
    //获取当前类的container和实例对象的objectContainer
    //classContainer
    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.
    //执行该对象所有hook的指定方法,以及该hook对象的类的实例方法
    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];
                break;
            }
        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
    }

    //执行该对象所有hook的指定方法,以及该hook对象的类的实例方法
    // 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)
    //如果原方法执行消息转发,且子类其或者其子类没有实现消息转发方法
    //则使用方法发备用的消息转发方法__aspects_forwardInvocation来执行
    //如果备用的消息转发方法__aspects_forwardInvocation也没实现,则crash
    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)];
}
复制代码

aspect_invoke

为执行hooks的宏,通过遍历aspects中的AspectIdentifier来执行hook方法,如果设置了 AspectOptionAutomaticRemoval枚举,则执行完毕后删除该hook方法

for (AspectIdentifier *aspect in aspects) {\
    [aspect invokeWithInfo:info];\
    if (aspect.options & AspectOptionAutomaticRemoval) { \
        aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \

    } \
}
复制代码

AspectToken的remove

实际调用的是aspect_remove方法,从container中移除hook

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];

            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;
}
复制代码

最后

总结一下aspects流程,其分为注册和响应

注册hooks

  1. aspects检查是否满足hook条件,包括黑名单,以及类方法hook校验等

  2. 对block进行签名,利用block的本来结构,通过指针位移获取到签名信息signature,获取block签名后并与selector参数进行校验

  3. 将签名信息放入container中,以便于响应hooks时调用

  4. 注册hooks相关内容,通过模仿KVO实现,新注册一个子类,将子类的class指向当前类,对子类的class方法重写,将消息转发方法调用 forwardInvocation 转交到 __ASPECTS_ARE_BEING_CALLED__方法调用,且预留__aspects_forwardInvocation作为消息转发的备用方法

5.替换原方法为 _objc_msgForward,调用原方法时会自动执行消息转发流程

响应hooks

1.获取对象和类对象的container

2.一次响应 before、instance、after的hooks,如果替换的方法没有实现(不是hook),则去父类查找并执行正常的消息转发流程,如果父类中消息转发都没实现,则在子类执行备用的__aspects_forwardInvocation 消息转发,否则crash

使用

从使用上来说,可以hook某一个实例对象的实例方法、hook一个类的所有实例方法、hook类方法

hook一个实例的实例方法,则是调用该实例对象的aspects对象实例方法进行hook

hook一个类的所有实例方法,则是调用该类方法进行hook,或者类对象的实例方法进行hook

hook一个类的类方法,则是调用该类对象的元类的实例方法进行hook

文章分类
iOS
文章标签