前言

Aspects框架也有一些缺陷,一不小心就会掉坑里面,我会通过源码解析进行说明。

源码解析

runtime有一定的了解,本文中不会对这些内容展开来讲,因为要把这些东西讲清楚,每一项都需要单独写一篇文章了。

主要流程解析

  1. Container,在这个过程中会进行一些前置条件的判断,例如这个方法是否支持被hook等,如果条件验证通过,就会把这次hook的信息保存起来,在方法调用的时候,查询出来使用。
  2. 第二个流程是动态创建子类,如果是针对类的hook,则不会走这一步。
  3. __ASPECTS_ARE_BEING_CALLED__,这个方法内部会查找到之前创建的Container,然后根据Container中的逻辑进行实际的调用。
  4. forwardInvocation:方法。

我对它的流程做了一个简化的图示,标有每个流程的序号,后面会对每个流程进行解析。流程如下:

其它子类路径。

①添加Container流程

这个流程中,把hook的逻辑封装成Container,并使用关联对象进行保存。这个过程中会判断hook的方法是否被支持、判断被hook类的继承关系、验证回调block正确性等操作。具体图示如下:

关键代码如下:

NSError **error) {
    ...
    aspect_performLocked(^{ // 加锁
        // hook前置条件判断
        self, selector, options, error)) {
            // 用selector作key,通过关联对象获得Container对象。
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            // 内部会判断block与hook的selector是否匹配,不匹配返回nil。
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                // 添加identifier,包含了hook的类型和回调。 
                [aspectContainer addAspect:identifier withOptions:options];

                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

NSError **error) {
    NSSet *disallowedSelectorList;
    dispatch_once_t pred;
    dispatch_once(&pred, ^{
        disallowedSelectorList = [nil];
    });

    // 这里对不支持hook的方法进行过滤
    NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NO;
    }

    // dealloc只支持AspectPositionBefore类型下调用
    AspectOptions position = options&AspectPositionFilter;
    NO;
    }

    // 判断是否存在这个方法
    self.class instancesRespondToSelector:selector]) {
        self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        NO;
    }

    // 这里禁止有继承关系的类hook同一个方法,代码量较多,不是关键内容,这里不贴出
    self))) {
        ...
    }

    YES;
}

/// AspectsContainer内部添加AspectIdentifier的实现。
/// 这里可以看出对同一个方法的多次hook都会被调用,不会出现后面hook的覆盖前面的情况。
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
    NSParameterAssert(aspect);
    NSUInteger position = options&AspectPositionFilter;
    switch (position) {
        break;
        break;
        break;
    }
}

复制代码
  1. forwardInvocation:进行实现的,所以对它的hook也不支持。
  2. AspectPositionAfter类型,调用时对象可能已经已经被释放了,从而引发野指针错误。
  3. issue,它报告了这样操作会导致死循环,我会在文章后面再进行说明。
  4. block进行hook的调用,涉及到方法参数的传递和返回值问题,所以其中会对block进行校验。

②runtime创建子类

具体说明请查看源码中的注释

// 执行hook
NSError **error) {
    NSCParameterAssert(selector);
    // 针对实例类型,会通过runtime动态创建子类。类类型则直接hook。
    Class klass = aspect_hookClass(self, error);
    ...
}

NSError **error) {
    self);
    Class statedClass = self.class;
	Class baseClass = object_getClass(self);
	NSStringFromClass(baseClass);

    // 已经被hook过的类,直接返回
	if ([className hasSuffix:AspectsSubclassSuffix]) {
		return baseClass;

    // 是元类(MetaClass),则代表是对类进行hook。(非单个实例)
	}if (class_isMetaClass(baseClass)) {
        // 内部是将类的forwardInvocation:方法替换为__ASPECTS_ARE_BEING_CALLED__
        self);
    // 可能是一个KVO对象等情况,传入实际的类型进行hook。
    }if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    // 单个实例的情况,动态创建子类进行hook.
	char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
	Class subclass = objc_getClass(subclassName);

	nil) {
		subclass = objc_allocateClassPair(baseClass, subclassName, 0);
		nil) {
            nil;
        }
        // 内部是将类的forwardInvocation:方法替换为__ASPECTS_ARE_BEING_CALLED__
		aspect_swizzleForwardInvocation(subclass);
        // 重写class方法,返回之前的类型,而不是新创建的子类。避免hook后,类型判断出现问题。
		aspect_hookedGetClass(subclass, statedClass);
		aspect_hookedGetClass(object_getClass(subclass), statedClass);
		objc_registerClassPair(subclass);
	}

	object_setClass(self, subclass);
	return subclass;
}

复制代码

③替换forwardInvocation:

__ASPECTS_ARE_BEING_CALLED__。源码如下:

void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    IMP originalImplementation = class_replaceMethod(klass, if (originalImplementation) {
        class_addMethod(klass, NSStringFromClass(klass));
}
复制代码

④hook方法交换IMP:

图示如下:

JSPatch,使用的时候需要注意,避免发生冲突。

被hook方法的调用流程

当hook注入后,对hook方法进行调用时,调用流程就会发生变化。图示如下:

因为Apsects对类的底层进行了修改,这种修改是基础方面的修改,需要考虑到各种场景和边界问题,一旦某方面考虑不周,就会引发出一些未知问题。另外这个框架是有缺陷的,很久没有进行更新了,我对它的已知问题点进行了总结,在下面进行说明。如果有未总结到位的,欢迎补充。

问题点

基于类的hooking,同一条继承链条上的所有类,一个方法只能被hook一次,后hook的无效。

issue

NSObject
- (void)foo;
@end

A
- (void)foo {
    @end

end

B
- (void)foo {
    // 导致死循环的代码
}
@end

char *argv[]) {
    [B aspect_hookSelector:NSArray *arguments) {
        NSArray *arguments) {
        // 调用后死循环
}
复制代码

forwardInvocation:,这样就导致了死循环。

针对单个实例的hook,hook后使用kvo没问题,使用kvo后hook会出现问题。

这里通过代码进行说明,以Animal对象为例:

NSObject
NSString * name;
@end

Animal
- (void)testKVO {
    [nil];
    void *)context {
    self.name);
}

- (void)dealloc {
    [@end

char * argv[]) {
    @autoreleasepool {
        Animal *animal = [[Animal alloc] init];
        [animal testKVO];
        // 这里如果改为针对类进行hook,则不会存在问题,因为类hook修改的是Animal类,而实例hook修改的是NSKVONotifying_Animal类
        [animal aspect_hookSelector:@selector(setName:) 
                        withOptions:AspectPositionAfter 
                         usingBlock:^(NSString *name){
            nil];
        // 这里会crash
        animal.name = 复制代码

Animal对象并没有这个方法的实现,这就导致了crash。

与category的共存问题

category进行hook,会导致crash。反之则没有问题。样例代码如下:

NSObject
NSString * name;
@end

Animal
- (NSString *)name {
    @end

hook)
+ (void)categoryHook;
@end

hook)
+ (void)categoryHook {
    dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class];
        SEL originalSelector = @selector(setName:);
        SEL swizzledSelector = @selector(lx_setName:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (NSString *)name {
    self lx_setName:name];
}
@end


char * argv[]) {
    @autoreleasepool {
        Animal *animal = [[Animal alloc] init];
        [Animal aspect_hookSelector:NSString *name){
            nil];
        
        [Animal categoryHook];
        // 调用后crash:[Animal lx_setName:]: unrecognized selector sent to instance 0x100608dc0
        animal.name = 复制代码

aspects__lx_setName,导致找不到方法而crash

基于类的hook,如果对同一个类同时hook类方法和实例方法,那么后hook的方法调用时会crash。样例代码如下:

NSObject
- (void)testInstanceMethod;
+ (void)testClassMethod;
@end

Animal
- (void)testInstanceMethod {
    void)testClassMethod {
    @end

char * argv[]) {
    @autoreleasepool {
        Animal *animal = [[Animal alloc] init];
        [Animal aspect_hookSelector:id<AspectInfo> aspectInfo){
            nil];
        
        [object_getClass([Animal id<AspectInfo> aspectInfo){
            nil];
        
        [animal testInstanceMethod];
        复制代码

aspect_swizzleClassInPlace方法中的逻辑缺陷导致的。

static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    // Animal类对象与Animal元类对象会得到同一个字符串。
    NSStringFromClass(klass);
    NSMutableSet *swizzledClasses) {
        // 类对象和元类对象得到同一个className,这里后加入的会被错误的过滤掉。
        if (![swizzledClasses containsObject:className]) {
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}
复制代码

IMP,导致了crash。

_objc_msgForward会出现冲突的问题

_objc_msgForward或相关逻辑的框架发生冲突。

性能问题

Coantiner进行调用,相对于未hook之前,额外增加了调用成本。所以不建议对频繁调用的方法和在项目中大量使用。

线程问题

OSSpinLock,存在线程反转的问题,在iOS10已经被标记为弃用。

对类方法的hook,需要使用object_getClass来获取元类对象进行hook

类方法进行hook,这里进行说明。

NSObject
+ (void)testClassMethod;
@end

// 需要通过object_getClass来获取元类对象进行hook
[object_getClass(Animal) aspect_hookSelector:@selector(testClassMethod)     
                                 withOptions:AspectPositionAfter 
                                  usingBlock:^(id<AspectInfo> aspectInfo){
    https://juejin.cn/post/7025783407540076581

相关文章:

  • 2021-06-09
  • 2022-01-21
  • 2021-11-22
  • 2021-09-06
  • 2023-03-15
  • 2021-07-05
  • 2022-12-23
猜你喜欢
  • 2021-04-18
  • 2021-08-25
  • 2021-05-31
  • 2021-07-18
  • 2021-04-19
  • 2021-04-27
  • 2022-01-13
相关资源
相似解决方案