美文网首页
Aspects源码学习笔记

Aspects源码学习笔记

作者: 我才是臭吉吉 | 来源:发表于2019-07-30 17:31 被阅读0次

    1. 相关结构说明

    Aspects.h

    AspectOptions

    切片位置,用于指定hook后自定义block执行的时机。

    AspectPositionAfter 原始IMP执行之后执行
    AspectPositionInstead 替换原始IMP
    AspectPositionBefore 原始IMP执行之前执行
    AspectOptionAutomaticRemoval 执行一次后直接移除
    AspectToken协议

    用于注销hook使用。使用其中的remove方法进行注销。

    AspectInfo协议

    作为插入的block的第一个参数出现。
    对外公开为协议,内部则使用类实现。

    方法 说明
    instance 被hook的实例对象
    originalInvocation 原始的NSInvocation对象
    arguments 原始参数数组
    Aspects,NSObject的分类

    Aspects的公开API核心。
    分为两个版本:实例方法版本和类方法版本。

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

    调用者为类对象,对指定类的selector进行hook,执行时机为options,插入的执行任务为block。可以监控错误,返回值为遵循AspectToken的对象,用它可以实现注销hook操作。

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

    作用与类方法版本相同,但调用者为类的实例对象,即hook的方法只对本实例有效,其他实例无效。

    AspectErrorCode

    错误信息说明。

    AspectErrorSelectorBlacklisted 黑名单(不允许hook的情况)
    AspectErrorDoesNotRespondToSelector 找不到SEL的实现
    AspectErrorSelectorDeallocPosition hook到delloc方法的时机错误(只允许before情况)
    AspectErrorSelectorAlreadyHookedInClassHierarchy 类继承体系中已经hook过该方法
    AspectErrorFailedToAllocateClassPair 创建类失败(实例对象hook时可能发生)
    AspectErrorMissingBlockSignature block的签名错误(编译期签名无效,无法读取使用)
    AspectErrorIncompatibleBlockSignature block签名与原始方法签名不匹配
    AspectErrorRemoveObjectAlreadyDeallocated 重复移除hook对象

    Aspect.m

    AspectBlockFlags

    切片block结构体中的位标识

    AspectBlockFlagsHasCopyDisposeHelpers 标识copy函数位(第25位)
    AspectBlockFlagsHasSignature 标识签名位(第30位)
    AspectBlockRef

    _AspectBlock结构体指针,结构与block结构体相似。

    AspectInfo

    主要用于包装NSInvocation对象

    遵循了AspectInfo协议,作为内部实现(外部只公开为协议,隐藏真正的类)。

    将协议方法实现为三个只读属性,使用指定方法进行初始化:

    - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
    
    AspectIdentifier

    保存切片的相关信息(如receiver、selector、block和error信息)。作为数据模型类。

    // 初始化方法,传入配置信息
    + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
    
    // 使用AspectInfo对象(NSInvocation封装类)执行方法,执行block任务
    - (BOOL)invokeWithInfo:(id<AspectInfo>)info;
    
    AspectsContainer

    作为AspectIdentifier的容器,负责管理内部的AspectIdentifier对象。提供添加、删除、检查等功能。
    根据options将AspectIdentifier对象分别存储在不同的类别数组中。

    在Aspects中,根据调用者(实例对象或类对象)实现了两个container。

    AspectTracker

    切片追踪者,保存着追踪的相关信息,方便查询。

    属性 说明
    trackedClass 追踪的类
    selectorNames 集合,保存着追踪的选择器名
    parentEntry 自身实例的指针,根据类继承体系指向子类对象
    NSInvocation + Aspects

    NSInvocation的分类,提供了方法:直接返回切片的所有参数。


    2. 实现源代码学习

    <u>Aspects是线程安全的</u>

    可以在三个方面验证此结论:

    1. 使用自旋锁进行整体hook

    我们知道,在公共API中,实现都是通过调用c函数aspect_add来完成的,而其中的执行环境是通过锁机制来保证线程安全的。

    static void aspect_performLocked(dispatch_block_t block) {
        // 创建自旋锁
        static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
        OSSpinLockLock(&aspect_lock);
        block();
        OSSpinLockUnlock(&aspect_lock);
    }
    

    使用自旋锁可以保证同时只执行一个运算任务,且其他运算单元不会因锁被他人保持而进入睡眠状态。自旋锁适合运算量不大的任务。在这种情况下,其效率要明显高于同步锁 @synchronized

    2. AspectContainer中使用原子性数组

    在类AspectContainer中,保存AspectIdentifier实例的数组属性(beforeAspectsinsteadAspectsafterAspects)的特性为atomic,保证在多线程环境下,访问该容器是安全的。

    3. 使用dispatch_once来保证共享数据只有一份
    /** 获取修改的类的集合(线程安全) */
    static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
        static NSMutableSet *swizzledClasses;
        static dispatch_once_t pred;
        dispatch_once(&pred, ^{
            swizzledClasses = [NSMutableSet new];
        });
        @synchronized(swizzledClasses) {
            block(swizzledClasses);
        }
    }
    

    这里使用dispatch_once保证集合创建为线程安全且只有一份。同时,执行block时也通过同步锁保证线程安全。

    <u>Aspects的hook方法不可高频次调用</u>

    作者说,hook的方法一般只可以是view或ViewController等方法,调用频次不要超过每秒1000次。这是由于Aspects的hook方法调用实际是在方法转发流程中进行的

    Aspects中所有的真正方法调用都是通过NSInvocation对象进行的:

    - (void)invoke;
    - (void)invokeWithTarget:(id)target;
    

    我们知道,完整的消息转发流程是在方法调用的最后一步才进行,苹果明确说明这是比较耗费性能的(需要通过获取方法签名NSMethodSignature对象,封装生成NSInvocation对象,然后在forwardInvocation:方法中执行invoke方法)。故作者添加了此说明。

    <u>Aspects对实例hook方法和类hook方法使用不同实现方案</u>

    对于类的实例来说,使用Aspects对某方法进行hook只是对本实例有效;
    而对于类对象来说,对某方法进行hook,即对本类的所有实例都有效。

    我们在aspect_hookClass的实现中,可以一探究竟

    static Class aspect_hookClass(NSObject *self, NSError **error) {
        NSCParameterAssert(self);
        Class statedClass = self.class; // 得到self所属的类(实例返回class,类对象返回自身)
        Class baseClass = object_getClass(self); // 获取self的class(获取isa:实例返回class对象,类对象返回metaClass)
        NSString *className = NSStringFromClass(baseClass);
    
        ...
    }
    

    首先我们看一下,对于class方法和objc_getClass函数的区别:

    <u>class方法</u>: 对于类,返回自身;对于实例对象,返回所属Class
    <u>objc_getClass函数</u>: 其实现都是返回调用者的isa。也就是说,对于实例对象,得到的是所属Class;对于类,得到的是metaClass。

    1. 类实例hook的实现
    static Class aspect_hookClass(NSObject *self, NSError **error) {
        ...
        
        // Already subclassed
        if ([className hasSuffix:AspectsSubclassSuffix]) {
            return baseClass; // 包含后缀,即hook过,直接返回class
        }
        
        ...
    
        // self是类的实例对象,正常情况
        // 动态创建一个类(添加后缀作为类名)
        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;
            }
    
            aspect_swizzleForwardInvocation(subclass); // 给这个新类交换forwardInvocation方法
            aspect_hookedGetClass(subclass, statedClass); // 新类的class方法,返回的是原class(瞒天过海,自己的类名还是原来的类)
            aspect_hookedGetClass(object_getClass(subclass), statedClass); // metaClass的class也指向原来的类
            // 注册此类
            objc_registerClassPair(subclass);
        }
    
        // 将self实例设置为此动态子类的实例
        object_setClass(self, subclass);
        return subclass;
    }
    

    在这里我们可以看到,对于实例hook,Aspects使用动态类的方式进行实现:创建一个继承于原类的子类,对该子类的NSInvocation进行hook,然后通过object_setClass将调用者的类指定为新子类**。

    下面我们依次查看方法实现:

    static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
    static void aspect_swizzleForwardInvocation(Class klass) {
        NSCParameterAssert(klass);
        // 替换原始类的forwardInvocation方法IMP(kclass没实现则自动添加),返回值是原IMP
        IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
        if (originalImplementation) {
            // 存在原IMP(即klass自己实现了forwardInvocation方法),则新SEL(hook版本的forwardInvocation)的IMP为原始IMP(即存储了原IMP)
            class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
        }
        // 完成了klass
        AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
    }
    

    通过class_replaceMethod,替换了klass的fowardInvocation: 方法实现(替换IMP为 ASPECTS_ARE_BEING_CALLED),klass没有实现此方法,则直接将此方法及实现添加到类中。
    当klass实现了原方法,则Aspects将原IMP保存到klass的AspectsForwardInvocationSelectorName方法中,相当于完成了forwardInvocation: 的方法交换。

    static void aspect_hookedGetClass(Class class, Class statedClass) {
        NSCParameterAssert(class);
        NSCParameterAssert(statedClass);
        Method method = class_getInstanceMethod(class, @selector(class));
        IMP newIMP = imp_implementationWithBlock(^(id self) {
            return statedClass;
        });
        // 将class的class实例方法的IMP替换为IMP的block实现(返回的是statedClass)
        class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
    }
    

    同理,此方法是将Class的class实例方法IMP进行替换,改为了statedClass。在调用时,即将新子类的Class及MetaClass均指向原类。这样原实例则根本不知道自己的实例已经被“偷梁换柱”了。

    2. 类对象hook的实现
    static Class aspect_hookClass(NSObject *self, NSError **error) {
        ...
        
        if (class_isMetaClass(baseClass)) {
            // 是metaClass,即self是class。需要swizzle这个class对象(Class)
            return aspect_swizzleClassInPlace((Class)self);
        }else if (statedClass != baseClass) {
            // statedClass与baseClass不同,即self也是class,self可能是一个KVO过的类,也需要swizzle这个class对象(metaClass:baseClass即KVO之后的中间类)才可以hook成功)
            return aspect_swizzleClassInPlace(baseClass);
        }
    
        ...
    }
    

    class_isMetaClass判断当前类是否为metaClass,通过这种情况,可以判定调用Aspects的hook方法的是类对象。所以调用者的目的是将所有的类实例的指定方法都进行hook。故Aspects直接对该类进行操作。

    注意
    由于KVO也是通过创建动态类的方式实现(创建子类后修改isa指向),故hook的应当是KVO之前的原始类。

    static Class aspect_swizzleClassInPlace(Class klass) {
        NSCParameterAssert(klass);
        NSString *className = NSStringFromClass(klass);
    
        _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
            // 在线程安全的情况下,将class加入到修改的类列表中
            if (![swizzledClasses containsObject:className]) {
                // 交换forwardInvocation方法
                aspect_swizzleForwardInvocation(klass);
                [swizzledClasses addObject:className];
            }
        });
        return klass;
    }
    

    这里直接对klass的forwardInvocation: 方法进行了替换,只不过需要在线程安全的前提下进行,且swizzledClasses是全局共享的。

    <u>Aspects对指定selector的替换过程</u>

    对于selector的处理过程,实例对象和类对象的处理方法是一致的。

    static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
        NSCParameterAssert(selector);
        Class klass = aspect_hookClass(self, error); // 已经hook过forwardInvocation方法的类
        
        // 准备检查hook对应的SEL
        Method targetMethod = class_getInstanceMethod(klass, selector); // 取出待hook的method
        IMP targetMethodIMP = method_getImplementation(targetMethod);
        // 查看SEL的IMP是否是已替换的
        if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
            // 创建Hook版本的SEL名称及类型编码
            const char *typeEncoding = method_getTypeEncoding(targetMethod);
            SEL aliasSelector = aspect_aliasForSelector(selector);
            if (![klass instancesRespondToSelector:aliasSelector]) {
                // 类没有实现此hook的方法,则添加上(使用原始IMP)
                __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);
            }
    
            // 给原SEL的实现修改为objc_msgForward函数
            // We use forwardInvocation to hook in.
            class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
            AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
        }
    }
    

    对于原始方法,Aspects将其实现IMP保存至aliasSelector中,而原始方法则直接指向 _objc_msgForward函数。

    _objc_msgForward 是方法调用过程中的一步,在objc_msgSend流程中,当方法未找到IMP时,且未能动态添加方法IMP,_objc_msgForward则开始执行。也就是说 _objc_msgForward是消息转发的起点。

    交换成功后,调用者执行原方法时,则会直接进入消息转发阶段。

    <u>Aspects对待hook的selector进行检测,符合要求才允许进行</u>
    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
        NSString *selectorName = NSStringFromSelector(selector);
        if ([disallowedSelectorList containsObject:selectorName]) {
            NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
            AspectError(AspectErrorSelectorBlacklisted, errorDescription);
            return NO;
        }
    
        // dealloc 只能在before时hook
        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;
        }
    
        // 类对象处理
        if (class_isMetaClass(object_getClass(self))) {
            ...
        }
    
        // 实例对象,直接允许
        return YES;
    }
    

    对于实例对象,只要待hook的selector符合上述要求,即可准备进行hook。
    而对于类而言,由于类存在继承体系,需要对hook的唯一性进行检测:

    static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
        ...
    
        // 类对象
        if (class_isMetaClass(object_getClass(self))) {
            Class klass = [self class];
            // 获取交换class的字典
            NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
            Class currentClass = [self class];
            do {
                // 获取当前类的追踪者对象
                AspectTracker *tracker = swizzledClassesDict[currentClass];
                // 查看追踪者的SEL列表中是否存在当前SEL
                if ([tracker.selectorNames containsObject:selectorName]) {
                    
                    // 已经追踪过当前SEL,查看追踪者的子类追踪者中,是否追踪过当前SEL
                    // Find the topmost class for the log.
                    if (tracker.parentEntry) {
                        // 追踪过当前SEL,报错(即父类要hook的SEL在子类中已经hook过了)
                        AspectTracker *topmostEntry = tracker.parentEntry;
                        while (topmostEntry.parentEntry) {
                            topmostEntry = topmostEntry.parentEntry;
                        }
                        NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
                        AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
                        return NO;
                    }else if (klass == currentClass) {
                        // 只是自己的类追踪过,才可以
                        return YES;
                    }
                }
            }while ((currentClass = class_getSuperclass(currentClass)));
    
            // 没有追踪过该类,则只设置从当前类开始hook
            currentClass = klass;
            AspectTracker *parentTracker = nil;
            do {
                // 只要子类hook该SEL,就将所有的父类也标识上(即从根类到当前类这个继承链都标记了hook当前SEL)
                // 懒加载追踪者对象,将SEL加入到追踪SEL列表中进行标记
                AspectTracker *tracker = swizzledClassesDict[currentClass];
                if (!tracker) {
                    tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
                    swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
                }
                [tracker.selectorNames addObject:selectorName];
                parentTracker = tracker;
            }while ((currentClass = class_getSuperclass(currentClass)));
        }
    
        ...
    }
    

    对于类对象来说,当类的指定selector准备hook之前,先要检测其继承体系中是否已经hook过此selector。
    其方法是:
    通过AspectTracker对象,对其内部的selectorNames数组进行检测,依照parentEntry向下依次查找(parentEntry存储的是子类对象),
    如果存在parentEntry的selectorNames已经包含selector,且该parentEntry类不是当前类,则证明selector已经在继承体系中(确切地说是子类中)已经hook过,不可以重复hook。

    <u>AspectIdentifier,切面信息的生成</u>

    我们回到最初,在aspect_add方法中可以看到,切面信息AspectIdentifier对象,是在开始hook操作之前,便生成添加到AspectsContainer容器中的:

    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(^{
            // SEL是否允许hook
            if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
                // 获取容器对象
                AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
                // 生成得到AspectIdentifier对象
                identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
                if (identifier) {
                    // 存在,即加入到容器中
                    [aspectContainer addAspect:identifier withOptions:options];
    
                    // 将self所属的class配置为,运行hook的版本
                    // (实例为创建动态类,交换消息转发实现;类为直接修改该lmetaClass,交换消息转发实现)
                    // Modify the class to allow message interception.
                    aspect_prepareClassAndHookSelector(self, selector, error);
                }
            }
        });
        return identifier;
    }
    

    首先,我们看一下如何得到保存AspectIdentifier对象的容器对象AspectContainer:

    // 获取SEL对应self所在的容器对象(内部加入的是带有前缀的SEL)
    static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
        NSCParameterAssert(self);
        // 获取别名SEL(用于hook原SEL)
        SEL aliasSelector = aspect_aliasForSelector(selector);
        // 获取self存储的切片容器对象【注意:由于self可以为实例对象或类对象,故NSObject中包含了两种AspectContainer属性:一个保存hook的实例方法identifier,一个保存hook的类方法identifier】
        AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
        if (!aspectContainer) {
            aspectContainer = [AspectsContainer new];
            objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
        }
        return aspectContainer;
    }
    

    从代码可以看出,AspectsContainer对象是通过关联对象的方式存储到NSObject+Aspects分类中的,其属性名称是使用hook版本的selector名称动态确定的。即每当我们hook一个selector后,NSObject类中即增加了一个名为“aspects_xxx”的属性,其类型为AspectsContainer。 对于加入其内部的AspectIdentifier对象,则根据options保存到不同的内部数组中。

    对于AspectIdentifier的生成过程,如下所示:

    /** 根据信息生成切面识别对象 */
    + (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);
        // 检查得到的签名对象是否符合要求
        if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
            return nil;
        }
    
        // 使用block签名的对象信息创建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对象包含完整的签名时,才可以生成AspectIdentifier对象。
    结合AspectBlockRef的结构数据,其生成过程如下:

    // Block内部定义的flag值.
    typedef NS_OPTIONS(int, AspectBlockFlags) {
        AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
        AspectBlockFlagsHasSignature          = (1 << 30)
    };
    
    // AspectBlockRef数据结构
    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;
            // 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) {
        // 转换为AspectBlockRef类型
        AspectBlockRef layout = (__bridge void *)block;
        
        // flags的值不是AspectBlockFlagsHasSignature,直接报错
        if (!(layout->flags & AspectBlockFlagsHasSignature)) {
            NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
            AspectError(AspectErrorMissingBlockSignature, description);
            return nil;
        }
        
        // 在descriptor结构数据中
        void *desc = layout->descriptor;
        
        // 跳过reserved和size
        desc += 2 * sizeof(unsigned long int);
        // 存在copy和dispose指针,跳过
        if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
            desc += 2 * sizeof(void *);
        }
        
        // 此时desc直接指向signature,没有值则认为没有签名
        if (!desc) {
            NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
            AspectError(AspectErrorMissingBlockSignature, description);
            return nil;
        }
        
        // 取出signature的值,转换生成NSMethodSignature对象
        const char *signature = (*(const char **)desc);
        return [NSMethodSignature signatureWithObjCTypes:signature];
    }
    

    检查生成的block的签名对象是否正确的方法,就是与原始selector的签名对象进行直接比较(比较每个参数是否相同):

    static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
        NSCParameterAssert(blockSignature);
        NSCParameterAssert(object);
        NSCParameterAssert(selector);
    
        BOOL signaturesMatch = YES;
        
        // 通过selector和object得到原始的方法签名
        NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
        
        // 与生成block签名比较参数个数
        if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
            // block签名参数更多,错误
            signaturesMatch = NO;
        }else {
            // block签名参数大于1时,查看第二个参数是否为对象(实质是AspectInfo实例)
            if (blockSignature.numberOfArguments > 1) {
                const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
                if (blockType[0] != '@') {
                    // 不是,错误
                    signaturesMatch = NO;
                }
            }
            
            // 由于标准方法签名的前两个参数为id和SEL,blockSignature对象的前两个参数为self和AspectInfo对象(执行时候是,现在只是id),故从第三个开始比较
            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:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature];
            AspectError(AspectErrorIncompatibleBlockSignature, description);
            return NO;
        }
        return YES;
    }
    
    <u>Aspects中hook完成后,方法的执行过程</u>

    方法的执行过程,即hook版本的forwardInvocation: 方法的执行过程,也就是 ASPECTS_ARE_BEING_CALLED 的实现过程:

    /** swizzled的forwardInvocation:方法 */
    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; // 将invocation的selector替换为hook的版本
        AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector); // 实例的切片信息容器
        AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector); // 类的切片信息容器
        AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation]; // 将invocation封装为AspectInfo对象
        NSArray *aspectsToRemove = nil;
    
        // Before hooks.
        // 依次使用类切片信息容器内的切片信息对象(AspectIdentifier)执行方法,传入info对象
        aspect_invoke(classContainer.beforeAspects, info);
        // 依次使用实例切片信息容器内的切片信息对象(AspectIdentifier)执行方法,传入info对象
        aspect_invoke(objectContainer.beforeAspects, info);
    
        // Instead hooks.
        BOOL respondsToAlias = YES;
        if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
            // 使用替换的AspectIdentifier执行
            aspect_invoke(classContainer.insteadAspects, info);
            aspect_invoke(objectContainer.insteadAspects, info);
        }else {
            // 原始调用者的类
            Class klass = object_getClass(invocation.target);
            // 找到可以执行hook版本的SEL的类,然后执行
            do {
                if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
                    [invocation invoke]; // 直接执行,aliasSelector对应的IMP原始IMP,也就是原始方法执行
                    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)
        // 没有找到类实现了hook的SEL
        if (!respondsToAlias) {
            // 将invocation恢复为原始SEL
            invocation.selector = originalSelector;
            // 获取原始forwardInvocation方法SEL(本来已经交换了)
            SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
            if ([self respondsToSelector:originalForwardInvocationSEL]) {
                // 原始实例可以响应(实现过forwardInvocation方法),则指向原始的消息转发流程
                ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
            }else {
                // 不能响应,则直接抛异常
                [self doesNotRecognizeSelector:invocation.selector];
            }
        }
    
        // 让需要移除的AspectIdentifier对象,依次执行remove方法,清除对应方法的hook状态
        [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
    }
    

    整个流程主要是:

    1. beforeBlock执行(如果有) -> 原始方法执行 -> afterBlock执行(如果有) -> 清除方法的hook状态(如果有)
    2. 原始方法实现如果不存在:如果原始类实现了forwardInvocation: 方法,则直接执行,走原始的消息转发流程了;如果没有实现,则直接抛异常(因为Aspects的流程已经走完)。其实整体来看,也是遵循原始类的方法调用过程。

    这里,需要看一下aspect_invoke函数的执行过程:

    // 定义成宏是为了可以得到调用栈的说明
    #define aspect_invoke(aspects, info) \
    for (AspectIdentifier *aspect in aspects) {\
        [aspect invokeWithInfo:info];\
        if (aspect.options & AspectOptionAutomaticRemoval) { \
            aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
        } \
    }
    

    此函数的作用是:

    1. 依次使用AspectIdentifier对象进行block调用,传入AspectInfo信息。
    2. 如果执行后需要移除此AspectIdentifier对象(清除hook状态),直接插入到数组中,最后统一清理。

    最后我们看一下,AspectIdentifier的invokeWithInfo方法是如何执行的:

    - (BOOL)invokeWithInfo:(id<AspectInfo>)info {
        // 使用block签名对象生成NSInvocation对象
        NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
        // 取出AspectInfo中原始类的invocation
        NSInvocation *originalInvocation = info.originalInvocation;
        
        // block签名中的参数个数
        NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
    
        if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
            // 参数个数不匹配(block中的参数过多)
            AspectLogError(@"Block has too many arguments. Not calling %@", info);
            return NO;
        }
    
        if (numberOfArguments > 1) {
            // block签名的参数个数大于1时,将AspectInfo对象设置在index为1的位置(这样block中的第一个参数即为AspectInfo对象)
            [blockInvocation setArgument:&info atIndex:1];
        }
        
        // 将原始invocation中的其他参数(除去原始的self和_cmd两个参数外的)copy到block的invocation中
        void *argBuf = NULL;
        for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
            const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
            NSUInteger argSize;
            NSGetSizeAndAlignment(type, &argSize, NULL);
            
            if (!(argBuf = reallocf(argBuf, argSize))) {
                AspectLogError(@"Failed to allocate memory for block invocation.");
                return NO;
            }
            
            [originalInvocation getArgument:argBuf atIndex:idx];
            [blockInvocation setArgument:argBuf atIndex:idx];
        }
        
        // blockInvocation执行(block执行)
        [blockInvocation invokeWithTarget:self.block];
        
        if (argBuf != NULL) {
            free(argBuf);
        }
        return YES;
    }
    
    <u>Aspects清除方法的hook状态,恢复原始实例/类</u>

    在外部,调用Aspects的API,hook完成相关方法后,得到的返回值为遵循AspectToken协议的对象。在内部,实际上是AspectIdentifier作为此协议的代理进行实现。故清除hook状态的实现即为AspectIdentifier的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; // 强引用,防止释放过程中object被释放
            if (self) {
                // 从容器中移除AspectIdentifier对象
                AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
                success = [aspectContainer removeAspect:aspect];
                
                // 清除调用者的hook状态,恢复selector和forwardInvocation的原始实现
                aspect_cleanupHookedClassAndSelector(self, aspect.selector);
                
                // 清理信息
                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;
    }
    

    其中,从hook状态恢复过程如下:

    static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
        NSCParameterAssert(self);
        NSCParameterAssert(selector);
    
        Class klass = object_getClass(self); // 调用者的类(instance->Class;Class->MetaClass)
        BOOL isMetaClass = class_isMetaClass(klass); // 判定是否为MetaClass
        if (isMetaClass) {
            // 即调用者为Class,klass设置为Class自身,以便统一处理,恢复selector(从这里可以看出,Aspects并不支持静态方法的hook,因为都是在Class上进行的事务)
            klass = (Class)self;
        }
    
        Method targetMethod = class_getInstanceMethod(klass, selector);
        IMP targetMethodIMP = method_getImplementation(targetMethod);
        if (aspect_isMsgForwardIMP(targetMethodIMP)) {
            // IMP是_objc_msgForward函数,证明hook过,需要恢复
            
            // 得到hook版本selector(其对应的IMP为原始IMP)
            const char *typeEncoding = method_getTypeEncoding(targetMethod);
            SEL aliasSelector = aspect_aliasForSelector(selector);
            Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
            // 取出原始IMP
            IMP originalIMP = method_getImplementation(originalMethod);
            NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
            
            // 替换回原始IMP
            class_replaceMethod(klass, selector, originalIMP, typeEncoding);
            AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
        }
    
        // 从全局追踪字典中移除本类(只对Class调用者有效)
        aspect_deregisterTrackedSelector(self, selector);
    
        // 查看AspectIdentifier的容器中是否还有其他对象
        AspectsContainer *container = aspect_getContainerForObject(self, selector);
        if (!container.hasAspects) {
            // AspectIdentifier容器空了(没有任何hook信息保存)
            
            // 清除此动态属性(间接删除了容器对象)
            aspect_destroyContainerForObject(self, selector);
    
            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); // 将调用者的类重新设置为原始类
                AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
                
                // 这里并没有在runtime系统中移除该类
            }else {
                // 调用者为类,将其复原(恢复forwardInvocation实现)
                if (isMetaClass) {
                    aspect_undoSwizzleClassInPlace((Class)self);
                }
            }
        }
    }
    

    首先,统一将selector的IMP替换回来。

    对于instance调用者,清除hook状态,只要将其isa指回到原来的类即可。

    对于Class调用者来说,则稍微麻烦一些:
    首先需要移除全局标记的方法信息:

    static void aspect_deregisterTrackedSelector(id self, SEL selector) {
        // 只对类调用者有效
        if (!class_isMetaClass(object_getClass(self))) return;
        
        // 取出全局swizzled的类字典
        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)));
    }
    

    对于关联对象,这里正好有个知识点:

    static void aspect_destroyContainerForObject(id<NSObject> self, SEL selector) {
        NSCParameterAssert(self);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        // 清除关联对象的值(给属性值清零)
        objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
    }
    

    由于关联对象并没有提供单独清除某一个属性的值的方法(只有全部清除),故Aspects使用设置相关对象值为nil的方式进行了清零
    顺便移除了AspectIdentifier的容器对象。

    对于复原类hook的forwardInvocation方法,也比较直接:

    static void aspect_undoSwizzleClassInPlace(Class klass) {
        NSCParameterAssert(klass);
        NSString *className = NSStringFromClass(klass);
    
        _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
            // 在线程安全的情况下,从修改的类列表中移除该类
            if ([swizzledClasses containsObject:className]) {
                // 恢复原始forwardInvocation方法
                aspect_undoSwizzleForwardInvocation(klass);
                [swizzledClasses removeObject:className];
            }
        });
    }
    
    static void aspect_undoSwizzleForwardInvocation(Class klass) {
        NSCParameterAssert(klass);
        
        // 得到hook版本的方法(对应的IMP即为备份的原始IMP)
        Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
        // NSObject原版方法
        Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
    
        // 如果klass没有实现forwardInvocation,则直接用NSObject的默认实现代替
        IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
        
        // 替换回原始实现
        class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
    
        AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
    }
    

    优秀代码解读:

    相关文章

      网友评论

          本文标题:Aspects源码学习笔记

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