美文网首页
Aspects 源码分析

Aspects 源码分析

作者: yxibng | 来源:发表于2022-01-11 09:18 被阅读0次

需求

  1. hook 实例对象的方法(仅该对象的方法被hook)
  2. hook 类对象的方法(该类所有对象调用该方法的时候,执行插入的block)
  3. hook 可以被移除
  4. 在被hook的方法执行前,执行后插入block,或者用block里的代码替换被hook的方法
  5. hook 插入的 block 被执行时,能拿到原始调用的信息InvocationInfo
  6. hook失败的时候,报告错误
  7. 接口设计尽可能简单小巧

实现

接口设计

  1. 通过category添加方法,减少侵入性,简单小巧
  2. 提供两个接口,一个实例方法,一个类方法,分别对应hook实例方法,hook 类对象的方法

接口定义

@interface NSObject (Aspects)

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

@end

参数:

  1. selector, 被hook的selector
  2. options, hook添加的block何时执行,调用前,调用后,还是替换实现
  3. block, hook操作插入的block,block 第一个参数会报告原始的调用信息,后面跟原始selector的参数
  4. error, 调用 hook 接口,出错信息

返回值:

  1. AspectToken,hook成功后,该hook 对应的标识,通过 remove 方法,可以移除该hook

源码分析

aspect_add

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(^{
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            //给类对象或实例对象绑定一个key为selector的AspectsContainer, 并获取
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            //创建一个AspectIdentifier,里面保存了 selector, self, option, block
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                //将identifier 添加到 container 中
                [aspectContainer addAspect:identifier withOptions:options];
                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

aspect_isSelectorAllowedAndTrack

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

    /*检查被hook的selector是否在黑名单*/
    NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }

    /*针对dealloc, 只能在dealloc之前添加执行block*/
    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;
    }
    /*如果hook的方法,没有实现,报错*/
    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;
    }
    /*
     1. 如果当前是一个实例变量, 返回 YES
     2. 如果当前self是一个类对象
        a. 顺着继承链往下,判断继承链上,是否哟子类已经hook对应的方法,如果有,报错
        b. 顺着继承链往上,判断是否有父类hook了对应的方法,
             i)如果是当前类,返回YES
            ii)如果是某个父类,报错
     3. 将当前类和顺着继承链向上的父类,标记为hook了该方法
     
     */
    // Search for the current class and the class hierarchy IF we are modifying a class object
    if (class_isMetaClass(object_getClass(self))) {
        //如果当前是一个类对象
        Class klass = [self class];
        NSMutableDictionary<Class, AspectTracker *> *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];
        //获取当前class 对应的 tracker
        AspectTracker *tracker = swizzledClassesDict[currentClass];
        //判断继承链上,是否有子类已经hook了对应的方法
        if ([tracker subclassHasHookedSelectorName:selectorName]) {
            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;
        }
        //判断继承链上,是否有父类hook了对应的方法
        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)));

        // Add the selector as being modified.
        currentClass = klass;
        AspectTracker *subclassTracker = nil;
        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_prepareClassAndHookSelector

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    /*
     1. self 是实例变量, 创建新类,替换新类的forwardInvoation: 实现为 __ASPECTS_ARE_BEING_CALLED__
     2. self 是类对象, 替换类对应的 forwardInvoation: 为 __ASPECTS_ARE_BEING_CALLED__
     */
    Class klass = aspect_hookClass(self, error);
    
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    /*
     如果hook的selector对应的实现不是_objc_msgForward
     selector -- aliasSelector
     添加aliasSelector
     
     交换实现
     selector - _objc_msgForward
     aliasSelector - origin imp

     之后调用 selector 会调用到 _objc_msgForward ,
     立即进入转发流程, 调用 forwardInvocation:
     
     forwardInvocation: 里面保留了原始的selector, target, 函数签名
     
     由于forwardInvocation: 的实现被替换为 __ASPECTS_ARE_BEING_CALLED__
     
     因此在 __ASPECTS_ARE_BEING_CALLED__ 里进行切片的处理
     */
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        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.
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}

aspect_hookClass

static Class aspect_hookClass(NSObject *self, NSError **error) {
    /*
    1. 如果是实例对象,获取该对象的class, 创建新的子类subclass,
        将子类的forwardInvocation: 对应的实现替换为__ASPECTS_ARE_BEING_CALLED__
        给子类添加新的selector(__aspects_forwardInvocation:), 该selector保存原始的forwardInvocation:的函数实现
        将当前实例对象的类(isa)指向创建的子类
    2. 如果是类对象
        将类对象 forwardInvocation: 对应的实现替换为__ASPECTS_ARE_BEING_CALLED__
        给类对象添加新的selector(__aspects_forwardInvocation:), 该selector保存原始的forwardInvocation:的函数实现
    */
    NSCParameterAssert(self);
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);

    // Already subclassed
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        return baseClass;
        // We swizzle a class object, not a single object.
    }else if (class_isMetaClass(baseClass)) {
        //如果当前是一个类对象
        return aspect_swizzleClassInPlace((Class)self);
        // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
    }else if (statedClass != 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) {
        //创建一个类,名字叫subclassName,继承自baseClass
        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;
        }
        /*
         subclass 方法替换
         forwardInvocation: --> (IMP)__ASPECTS_ARE_BEING_CALLED__
         添加 AspectsForwardInvocationSelectorName --> originalImplementation of forwardInvocation:
         */
        aspect_swizzleForwardInvocation(subclass);
        /*
         subclass 方法替换
         使对应的class 方法 返回原始类 statedClass
         */
        aspect_hookedGetClass(subclass, statedClass);
        /*
         subclass 元类 方法替换
         使对应的class 方法 返回原始类 statedClass 的元类
         */
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        //将创建的子类注册
        objc_registerClassPair(subclass);
    }
    //将当前实例对象的类修改为创建的类
    object_setClass(self, subclass);
    return subclass;
}

ASPECTS_ARE_BEING_CALLED

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    /*
     
     1. 获取原始的selector
     2. 如果该selector是被hook的selector,获取被替换成的aliasSelector
     3. 替换 invocation.selector = aliasSelector
     4. 实例变量的处理,通过 aliasSelector, 获取当前对象上绑定的 AspectsContainer
     5. 类对象的处理,通过 aliasSelector,获取当前类对应的 AspectsContainer
     6. 分别对实例变量 和 类对象 绑定的 AspectsContainer 进行调用
     7. 对需要移除的identifier在调用后做自动清理
     */
    
    
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    //获取invocation 对应的原始的selector
    SEL originalSelector = invocation.selector;
    //获取被替换成的aliasSelector(保存了原始的selector对应的IMP)
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    //将invocation selector 替换为 aliasSelector
    invocation.selector = aliasSelector;
    //获取实例变量上绑定的hook
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    //获取类对象上绑定的hook
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    //获取hook info
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

    //执行调用前hook列表
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);

    //调用selector 被更换实现的hook
    BOOL respondsToAlias = YES;
    if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
        aspect_invoke(classContainer.insteadAspects, info);
        aspect_invoke(objectContainer.insteadAspects, info);
    }else {
        //hook 操作列表为空,正常执行
        Class klass = object_getClass(invocation.target);
        do {
            if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
                [invocation invoke];
                break;
            }
        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
    }
    //执行调用后hook列表
    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];
        }
    }

    //移除被标记为 调用后自动移除的 hook
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

aspect_remove

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) {
             /*
              1. self 是对象
              2. self 是类
              获取对应self上关联的AspectsContainer
              */
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
            //从container 中移除该 aspect
            success = [aspectContainer removeAspect:aspect];
            //清理该selector在self上的hook
            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;
}

aspect_cleanupHookedClassAndSelector

static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    
    /*
     获取该hook对应的类
     1. 实例变量,对应的类是新创建的子类
     2. 本身就是类对象,返回自身
     */
    Class klass = object_getClass(self);
    BOOL isMetaClass = class_isMetaClass(klass);
    if (isMetaClass) {
        klass = (Class)self;
    }
    
    /*
     原来的selector 对应的IMP 被替换成了 _objc_msgForward, 恢复成原来的 IMP
     */
    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));
    }

    /*
     从类的继承链中,移除对该selector hook的记录
     */
    aspect_deregisterTrackedSelector(self, selector);

    // 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) {
        /*
         如果hook容器中的元素为空,将container销毁
         */
        aspect_destroyContainerForObject(self, selector);

        // Figure out how the class was modified to undo the changes.
        NSString *className = NSStringFromClass(klass);
        if ([className hasSuffix:AspectsSubclassSuffix]) {
            /*
             如果是对象,对象的ISA被替换了
             恢复当前对象的ISA为原来的类
             */
            Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
            NSCAssert(originalClass != nil, @"Original class must exist");
            object_setClass(self, originalClass);
            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 {
            /*
             如果当前是类对象,恢复 forwardInvocation: 指向原来的 IMP
             */
            if (isMetaClass) {
                aspect_undoSwizzleClassInPlace((Class)self);
            }else if (self.class != klass) {
                aspect_undoSwizzleClassInPlace(klass);
            }
        }
    }
}

相关文章

  • iOS 引入 Aspects 吧,迟早会用到!

    引入 Aspects 吧,迟早会用到! 关于 AOP 在 iOS 中的应用,以及 Aspects 的源码分析文章...

  • Aspects 源码分析

    Method Swizzling 和 AOP 实践 这里唯一可能需要解释的是 class_addMethod 。要...

  • Aspects源码分析

    Aspects一直在项目中使用,主要使用AOP方式进行tracking,但还没好好看一看,最近研究了一下源码,十分...

  • Aspects 源码分析

    Aspects 分析- (id)aspect_hookSelector:(SEL)sel...

  • Aspects 源码分析

    需求 hook 实例对象的方法(仅该对象的方法被hook) hook 类对象的方法(该类所有对象调用该方法的时候,...

  • IOS中AOP框架Aspects源码分析

    IOS中AOP框架Aspects源码分析 AOP是Aspect Oriented Programming的缩写,意...

  • Aspects源码浅析

    Aspects源码浅析 同步发布到博客地址Aspects源码浅析 Aspects 可以很方便的让我们 hook 要...

  • Aspects源码解析

    Aspects 源码:https://github.com/steipete/Aspects 基本概述,此框架提供...

  • Aspects 源码解读

    Aspects 源码解读 1.Aspects简介 Aspects是一种面向切面编程,相对于继承而已,无需改动目标源...

  • Aspects改进尝试

    背景 一个库:Aspects两篇文章:面向切面编程之 Aspects 源码解析及应用消息转发机制与Aspects源...

网友评论

      本文标题:Aspects 源码分析

      本文链接:https://www.haomeiwen.com/subject/allbcrtx.html