Aspects源码学习笔记

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

优秀代码解读:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容