美文网首页
Aspects源码解析

Aspects源码解析

作者: 奚山遇白 | 来源:发表于2020-03-26 10:51 被阅读0次

1 aspects简介

1.1 Aspect Oriented Programming简介

面向切面编程(Aspect Oriented Programming, AOP)是指针对业务处理过程中的切面进行提取,将某一部分功能提取出来与原有对象进行隔离,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

1.2 什么是Aspects

Aspects是一个面向切面的轻量级库。它允许在运行时向每一个对象和实例存在的方法里面插入功能代码,并且可以选择在目标方法执行的切入点插入(AspectOptions:after(目标方法执行之后)/instead(替换原来的方法执行)/before(在原来方法之前执行))。

1.3 Aspects实现原理

在一个函数被调用时会有如下图所示的操作流程:


ForwardInvocation

从上图可知:在函数调用时,如果 selector 有对应的 IMP ,则直接执行,如果没有,oc给我们提供了几个可供补救的机会,依次有 resolveInstanceMethod 、forwardingTargetForSelector、forwardInvocation。
Aspects之所以选择在 forwardInvocation 这里处理是因为,这几个阶段特性都不太一样:
resolvedInstanceMethod: 适合给类/对象动态添加一个相应的实现
forwardingTargetForSelector:适合将消息转发给其他对象处理
forwardInvocation: 是里面最灵活,最能符合需求的
因此 Aspects的方案就是,对于待 hook 的 selector,将其指向 objc_msgForward / _objc_msgForward_stret ,同时生成一个新的 aliasSelector 指向原来的 IMP,并且 hook住 forwardInvocation函数,通过forwardInvocation调用到原来的IMP。

核心原理:按照上面的思路,当被 hook 的 selector 被执行的时候,首先根据 selector找到了 objc_msgForward / _objc_msgForward_stret ,而这会进入 forwardInvocation,同时由于forwardInvocation 的指向也被修改了,因此会转入新的 forwardInvocation函数,在里面执行需要嵌入的附加代码,完成之后,再转回原来的 IMP。

2 aspects.h

aspects.h文件大致结构

2.1 AspectOptions

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            /// Called after the original implementation (default)
    AspectPositionInstead = 1,            /// Will replace the original implementation.
    AspectPositionBefore  = 2,            /// Called before the original implementation.
    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};

AspectOptions定义是为了区分切片调用的时机,默认是AspectPositionAfter。
AspectPositionAfter在原方法执行之后调用切片方法
AspectPositionInstead是替换原方法,仅执行切片方法。
AspectPositionBefore是在原方法执行之前调用切片方法。
AspectOptionAutomaticRemoval是在hook执行完自动移除,此处需注意,该选项的执行时机是在原方法执行之后调用切片方法。

2.2 AspectToken

/// Opaque Aspect Token that allows to deregister the hook.
@protocol AspectToken <NSObject>

/// Deregisters an aspect.
/// @return YES if deregistration is successful, otherwise NO.
- (BOOL)remove;

@end

AspectToken协议是用来注销一个hook。remove方法返回YES代表撤销成功,返回NO撤销失败。

2.3 AspectInfo

/// The AspectInfo protocol is the first parameter of our block syntax.
@protocol AspectInfo <NSObject>

/// The instance that is currently hooked.
- (id)instance;

/// The original invocation of the hooked method.
- (NSInvocation *)originalInvocation;

/// All method arguments, boxed. This is lazily evaluated.
- (NSArray *)arguments;

@end

AspectInfo协议是切片block返回的第一个参数,instance方法返回当前被hook的实例。originalInvocation方法返回被hooked方法的原始的invocation。arguments方法返回所有方法的参数。它的实现是懒加载。

2.4 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.
// 需要注意:为一个指定的类的某个方法执行hook.对这个类的所有对象都会起作用.
+ (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.
// 为一个指定的对象的某个方法执行hook,只作用于当前对象.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

Aspects利用OC的消息转发机制来hook。这样会有一些性能的开销,不能将Aspects加到经常被调用的方法里面。Aspects是设计给view/controller代码使用的,而不是用来hook每秒调用1000次的方法。

添加Aspects之后,会返回一个隐式的token即AspectToken,这个token会被用来注销hook方法。

NSObject+Aspects 里面只有两个方法可以调用的方法,只要是NSObject对象都可以使用这两个方法。
selector:增加切面的原有方法。
options:调用的时机枚举。
block:复制了原有方法签名,block遵循Aspectinfo协议,处理切面功能的代码。
id<AspectToken>:用来注销hook的对象。

注意:Aspects不支持hook静态的static方法。

2.5 AspectErrorCode

typedef NS_ENUM(NSUInteger, AspectErrorCode) {
    AspectErrorSelectorBlacklisted,                   /// Selectors like release, retain, autorelease are blacklisted.
    AspectErrorDoesNotRespondToSelector,              /// Selector could not be found.
    AspectErrorSelectorDeallocPosition,               /// When hooking dealloc, only AspectPositionBefore is allowed.
    AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
    AspectErrorFailedToAllocateClassPair,             /// The runtime failed creating a class pair.
    AspectErrorMissingBlockSignature,                 /// The block misses compile time signature info and can't be called.
    AspectErrorIncompatibleBlockSignature,            /// The block signature does not match the method or is too large.

    AspectErrorRemoveObjectAlreadyDeallocated = 100   /// (for removing) The object hooked is already deallocated.
};

extern NSString *const AspectErrorDomain;

AspectErrorCode 定义了错误码的类型,方便hook失败时候的代码调试。

3 aspects.m

aspects.m文件大致结构

3.1 AspectBlockRef

// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
    AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
    AspectBlockFlagsHasSignature          = (1 << 30)
};
typedef struct _AspectBlock {
    __unused Class isa;
    AspectBlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct _AspectBlock *block, ...);
    struct {
        unsigned long int reserved;
        unsigned long int size;
        // 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;

AspectBlockFlags 可选枚举,对block做标记:
AspectBlockFlagsHasCopyDisposeHelpers:是否增加copy和dispose函数指针。
AspectBlockFlagsHasSignature:是否需要函数签名。

3.2 AspectsInfo

@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end
@implementation AspectInfo

@synthesize arguments = _arguments;

- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
    // 首先验证参数有效
    NSCParameterAssert(instance);
    NSCParameterAssert(invocation);
    // 然后赋值保存实例/原方法
    if (self = [super init]) {
        _instance = instance;
        _originalInvocation = invocation;
    }
    return self;
}

- (NSArray *)arguments {
    // Lazily evaluate arguments, boxing is expensive.
    if (!_arguments) {
        _arguments = self.originalInvocation.aspects_arguments;
    }
    return _arguments;
}

@end

AspectInfo继承于NSObject,实际上是protocol。
在其 - (id)initWithInstance: invocation:方法中,把外面传进来的实例instance,和原始的invocation保存到AspectInfo类对应的成员变量中。
而- (NSArray *)arguments是一个懒加载方法,返回的是原始的invocation里面的aspects参数数组。

3.3 AspectIdentifier

// Tracks a single aspect.
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end
@implementation 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); // TODO: check signature compatibility, etc.
    // 用来比较block的函数签名和原来方法签名是否匹配
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }

    // 赋值
    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;
}

- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    NSInvocation *originalInvocation = info.originalInvocation;
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;

    // Be extra paranoid. We already check that on hook registration.
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        AspectLogError(@"Block has too many arguments. Not calling %@", info);
        return NO;
    }

    // The `self` of the block will be the AspectInfo. Optional.
    // 如果参数`block`中的参数大于1个,则把包装成`AspectInfo `
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    
    // 从`originalInvocation `中依次取出参数给`blockInvocation `赋值
    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 invokeWithTarget:self.block];
    
    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}

- (BOOL)remove {
    return aspect_remove(self, NULL);
}

@end

AspectIdentifier是一个切片Aspect的具体内容。其中包含了单个的 Aspect 的具体信息,包括执行时机,要执行 block 所需要用到的具体信息:包括方法签名、参数等等。初始化AspectIdentifier的过程实质是把我们传入的block打包成AspectIdentifier。其中aspect_isCompatibleBlockSignature方法用来比较block的函数签名和原来方法签名是否匹配。

3.3.1 aspect_blockMethodSignature

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    // 将block强制转换成AspectBlockRef
    AspectBlockRef layout = (__bridge void *)block;
    // 判断是否有方法签名
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    void *desc = layout->descriptor;
    
    // sizeof()功能:计算数据空间的字节数,32位和64位有些数据类型是不等的
    desc += 2 * sizeof(unsigned long int);
    
    // 根据不同AspectBlockFlags标志味设置移动对应位置到signature
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);
    }
    if (!desc) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

这里将外部的block强制转换成AspectBlockRef,然后判断flags标志位是否包含AspectBlockFlagsHasSignature,如果没有则表示block没有包含方法签名。
desc就是原来block里面对应的descriptor指针。descriptor指针往下偏移2个unsigned long int的位置就指向了copy函数的地址,然后判断flags标志位是否包含AspectBlockFlagsHasCopyDisposeHelpers,那么继续往下偏移2个(void *)的大小。这时指针肯定移动到了const char *signature的位置。如果desc不存在,那么也会报错,该block不包含方法签名。
最后 [NSMethodSignature signatureWithObjCTypes:signature] 返回函数签名。

3.4 AspectsContainer

// Tracks all aspects for an object/class.
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
// 这里使用atomic -atomic保证了getter和setter存取方法的线程安全,相当于函数头尾加了锁一样,每次只能有一个线程调用对象的setter方法,所以可以保证数据的完整性 // TODO:
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end

AspectsContainer 是对象和类的所有的Aspects容器,addAspect会按照切面的时机分别把切片Aspects放到对应的数组里面。removeAspects会循环移除所有的Aspects。hasAspects判断是否有Aspects。其属性的三个数组分别用来存储对应切面时机的Aspects。

3.5 AspectTracker

// 用来跟踪要被hook的类
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
// 被hook的类
@property (nonatomic, strong) Class trackedClass;
// 被hook的类名
@property (nonatomic, readonly) NSString *trackedClassName;
// 被hook的方法名
@property (nonatomic, strong) NSMutableSet *selectorNames;
// 字典,key是hookingSelectorName,value是装满AspectTracker的NSMutableSet。
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
// 把AspectTracker加入到对应selectorName的集合中
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
// 把AspectTracker从对应的selectorName的集合中移除
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
// 是否该方法已经被hook
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end
@end    
@implementation AspectTracker

- (id)initWithTrackedClass:(Class)trackedClass {
    if (self = [super init]) {
        _trackedClass = trackedClass;
        _selectorNames = [NSMutableSet new];
        _selectorNamesToSubclassTrackers = [NSMutableDictionary new];
    }
    return self;
}

- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
    return self.selectorNamesToSubclassTrackers[selectorName] != nil;
}

- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
    if (!trackerSet) {
        trackerSet = [NSMutableSet new];
        self.selectorNamesToSubclassTrackers[selectorName] = trackerSet;
    }
    [trackerSet addObject:subclassTracker];
}
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
    [trackerSet removeObject:subclassTracker];
    if (trackerSet.count == 0) {
        [self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName];
    }
}
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName {
    NSMutableSet *hookingSubclassTrackers = [NSMutableSet new];
    for (AspectTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) {
        if ([tracker.selectorNames containsObject:selectorName]) {
            [hookingSubclassTrackers addObject:tracker];
        }
        [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]];
    }
    return hookingSubclassTrackers;
}
- (NSString *)trackedClassName {
    return NSStringFromClass(self.trackedClass);
}
@end

AspectTracker这个类是用来跟踪要被hook的类。trackedClass是被追踪的类。trackedClassName是被追踪类的类名。selectorNames是一个NSMutableSet,这里会记录要被hook替换的方法名,用NSMutableSet是为了防止重复替换方法。selectorNamesToSubclassTrackers是一个字典,key是hookingSelectorName,value是装满AspectTracker的NSMutableSet。

addSubclassTracker方法是把AspectTracker加入到对应selectorName的集合中。removeSubclassTracker方法是把AspectTracker从对应的selectorName的集合中移除。subclassTrackersHookingSelectorName方法是一个并查集,传入一个selectorName,通过递归查找,找到所有包含这个selectorName的set,最后把这些set合并在一起作为返回值返回。

3.6 NSObject+Aspects HOOk方法实现

3.6.1 hook接口函数

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

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

两个方法调用的是同一个方法 aspects_add,具体查看aspects_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)) {
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

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

aspect_performLocked是一个自旋锁,这里选择自旋锁是因为它的高效(不过自旋锁存在问题:如果访问这个锁的线程不是同一优先级的话,会有死锁的潜在风险,具体查看不再安全的OSSpinLock)。

然后我们来看一看block做了哪些事情。

1.对出入进来的参数进行检验,保证参数合法。
2.创建aspect容器,注意容器是懒加载形式动态添加到NSObject分类中作为属性。
3.根据参数,比如selector,option,创建AspectIdentifier实例,上面已经说过AspectIdentifier主要包含了单个的 Aspect的具体信息,包括执行时机,要执行block 所需要用到的具体信息。
4.将单个的 Aspect 的具体信息加到属性aspectContainer中。
5.最为重要的部分进行Hook操作,生成子类,类型编码处理,方法替换等。


flow

下面我们来逐步分析:

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

    // Check against the blacklist.
    NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }

    // Additional checks.
    AspectOptions position = options&AspectPositionFilter;
    if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
        NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }

    if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        return NO;
    }

    // Search for the current class and the class hierarchy IF we are modifying a class object
    if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];

        AspectTracker *tracker = swizzledClassesDict[currentClass];
        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;
        }

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

首先创建一个NSSet对象实例,这里面是一个“黑名单”,是不允许hook的函数名。retain, release, autorelease, forwardInvocation:是不允许被hook的。如果检测到selector名在”黑名单“中,立即报错。
然后继续检查selector是否是dealloc,并且切入点是否是before,否则直接报错。
上面检测通过之后,继续检查self和self.class方法列表中是否存在selector,如果不存在直接报错。
class_isMetaClass 先判断是不是元类。接下来的判断都是判断元类里面能否允许被替换方法。
subclassHasHookedSelectorName会判断当前tracker的subclass里面是否包含selectorName。因为一个方法在一个类的层级里面只能被hook一次。如果tracker里面已经包含了一次,那么会报错。

经过上面合法性hook判断和类方法不允许重复替换的检查后,到此,就可以把要hook的信息记录下来,用AspectTracker标记。在标记过程中,一旦子类被更改,父类也需要跟着一起被标记。do-while的终止条件还是currentClass = class_getSuperclass(currentClass)。以上是元类的类方法hook判断合法性的代码。

总结:如果不是元类,只要不是hook"retain", "release", "autorelease", "forwardInvocation:"4种方法,而且hook “dealloc”方法的时机必须是before,并且selector能被找到,那么方法就可以被hook。通过了selector是否能被hook合法性的检查之后,就要获取或者创建AspectsContainer容器了。

3.6.3 aspect_prepareClassAndHookSelector

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    // 获取hook类
    Class klass = aspect_hookClass(self, error);
    // 根据方法名获取hook类中的对应method
    Method targetMethod = class_getInstanceMethod(klass, selector);
    // 获取对应imp指针
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    // 判断当前IMP是不是_objc_msgForward或者_objc_msgForward_stret,即判断当前IMP是不是消息转发
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        // 获取一个标记了aspects的方法名的别名
        SEL aliasSelector = aspect_aliasForSelector(selector);
        // 若hook获得的子类没有这个方法,添加方法到子类
        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.
        // 将需要替换的selector,指向_objc_msgForward,进行统一处理
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}

从上面代码片段可以看出,aspect_prepareClassAndHookSelector执行主要分四步操作,aspect_hookClass,aspect_isMsgForwardIMP,aspect_aliasForSelector,aspect_getMsgForwardIMP。

3.6.3.1 aspect_hookClass
static Class aspect_hookClass(NSObject *self, NSError **error) {
    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.
    // hook 是在runtime中动态创建子类的基础上实现的
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        // 使用原来类和新的子类名创建子类
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        if (subclass == nil) {
            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }

        // 更换新建子类的forwardInvocation方法
        aspect_swizzleForwardInvocation(subclass);
        // 交换imp指针到新建子类
        aspect_hookedGetClass(subclass, statedClass);
        // 交换imp指针到新建子类的类
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        objc_registerClassPair(subclass);
    }

    object_setClass(self, subclass);
    // 返回新建子类
    return subclass;
}

先判断是用来className是否包含hasSuffix:AspectsSubclassSuffix,如果包含了@"Aspects"后缀,代表该类已经被hook过了,直接return。

如果不包含@"Aspects"后缀,再判断baseClass是否是元类,如果是元类,调用aspect_swizzleClassInPlace。如果也不是元类,再判断statedClass 和 baseClass是否相等,如果不相等,说明为KVO过的对象,因为KVO的对象isa指针会指向一个中间类。对KVO中间类调用aspect_swizzleClassInPlace。

当className没有包含@"Aspects"后缀,并且也不是元类,也不是KVO的中间类,即statedClass = = baseClass 的情况,于是,默认的新建一个子类subclass。

到此,我们可以了解到Aspects的设计思想,hook 是在runtime中动态创建子类的基础上实现的。所有的 swizzling 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类,也就是,当你在 remove aspects 的时候,如果发现当前对象的 aspect 都被移除了,那么,你可以将 isa 指针重新指回对象本身的类,从而消除了该对象的 swizzling ,同时也不会影响到其他该类的不同对象)这样对原来替换的类或者对象没有任何影响而且可以在子类基础上新增或者删除aspect。

新建的类的名字,会先加上AspectsSubclassSuffix后缀,即在className后面加上@"Aspects",标记成子类。再调用objc_getClass方法,创建这个子类。

aspect_hookedGetClass方法是把class的实例方法替换成返回statedClass,也就是说把调用class时候的isa指向了statedClass了。

3.6.3.1.1 aspect_swizzleClassInPlace
static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);

    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if (![swizzledClasses containsObject:className]) {
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}

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

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    if (originalImplementation) {
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

_aspect_modifySwizzledClasses会传入一个入参为(NSMutableSet *swizzledClasses)的block,block里面就是判断在这个Set里面是否包含当前的ClassName,如果不包含,就调用aspect_swizzleForwardInvocation()方法,并把className加入到Set集合里面。

aspect_swizzleForwardInvocation ,class_replaceMethod返回的是原方法的IMP ,originalImplementation不为空的话说明原方法有实现,添加一个新方法_aspects_forwardInvocation:指向了原来的originalImplementation。

3.6.3.2 ASPECTS_ARE_BEING_CALLED

// This is the swizzled forwardInvocation: method.
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;
    // 该对象的AspectsContainer
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    // 这个类的AspectsContainer
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

    // Before hooks.
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);

    // Instead hooks.
    BOOL respondsToAlias = YES;
    if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
        aspect_invoke(classContainer.insteadAspects, info);
        aspect_invoke(objectContainer.insteadAspects, info);
    }else {
        Class klass = object_getClass(invocation.target);
        do {
            if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
                [invocation invoke];
                break;
            }
        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
    }

    // 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后的方法,添加/执行doesNotRecognizeSelector
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            [self doesNotRecognizeSelector:invocation.selector];
        }
    }

    // Remove any hooks that are queued for deregistration.
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

hook准备工作:

  • 获取原始的selector。
  • 获取带有aspects_xxxx前缀的方法。
  • 替换selector。
  • 获取实例对象的容器objectContainer,这里是之前aspect_add关联过的对象。
  • 获取获得类对象容器classContainer。
  • 初始化AspectInfo,传入self、invocation参数。

aspects_invoke这个核心替换的方法,能hook我们原有的SEL。对应的,函数第一个参数分别传入的是classContainer.beforeAspects、classContainer.insteadAspects、classContainer.afterAspects就能对应的实现before、instead、after对应时间的Aspects切片的hook。

如果以上步骤hook没有被正常执行,那么就会执行原来的方法,并且最后调用remove移除hook。

3.6.3.3 aspect_isMsgForwardIMP
static BOOL aspect_isMsgForwardIMP(IMP impl) {
    return impl == _objc_msgForward
#if !defined(__arm64__)
    || impl == (IMP)_objc_msgForward_stret
#endif
    ;
}

这里是判断当前IMP是不是_objc_msgForward或者_objc_msgForward_stret,即判断当前IMP是不是消息转发。

3.6.3.4 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) {
            // 获取容器中存储的
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
            // 移除数据
            success = [aspectContainer removeAspect:aspect];
            
            // 恢复类名及方法列表
            aspect_cleanupHookedClassAndSelector(self, aspect.selector);
            // destroy token
            aspect.object = nil;
            aspect.block = nil;
            aspect.selector = NULL;
        }else {
            NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
            AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
        }
    });
    return success;
}

a spect_remove 是整个 aspect_add的逆过程。 aspect_performLocked是保证线程安全。

把AspectsContainer都置为空,remove最关键的过程就是aspect_cleanupHookedClassAndSelector(self, aspect.selector),移除之前hook的class和selector。

参考链接:
Aspects源码解析

相关文章

  • Aspects改进尝试

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

  • Aspects 源码解析

    简介 Aspects是一个轻量的面向切面编程(AOP)的第三方库,面向切面编程简单来说,就是在原来的业务流程之中的...

  • Aspects源码解析

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

  • Aspects源码解析

    A delightful, simple library for aspect oriented programm...

  • Aspects源码解析

    1 aspects简介 1.1 Aspect Oriented Programming简介 面向切面编程(Aspe...

  • Aspects源码解析

    1. 简介 Aspects是一个令人愉悦的、简单的面向切面编程的库。 可以把 Aspects 看成是一个 meth...

  • runtime系列文章总结

    《iOS Runtime详解(消息机制,类元对象,缓存机制,消息转发)》《消息转发机制与Aspects源码解析》《...

  • Aspects源码浅析

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

  • Aspects解析

    Aspects解析

  • Aspects 源码解读

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

网友评论

      本文标题:Aspects源码解析

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