Aspects源码分析
Categories: Study
最近有个需求用到了Aspects这个库,用的过程中发现了一些问题,为了解决问题,花了几天时间精读了一遍Aspects的源码。读的过程中发现这个库如果发散着去读,可以了解Objective-C这个语言的方方面面。这篇文章就不发散了,本篇集中记录Aspects如何去进行Hook方法的。后面可能会写一些Aspects发散的文章。本篇分析采用从入口方法开始,然后用到哪个方法会分析哪个方法。
Aspects入口方法
Aspects为NSObject
写了一个分类,添加了两个方法,一个是类方法,一个是实例方法,方法名都一样,具体方法如下:
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
这两个方法都直接调用了aspect_add
方法,这个方法才是hook的真正入口,这个方法的实现如下:
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
__block AspectIdentifier *identifier = nil;
// 1. 加锁,保证线程安全
aspect_performLocked(^{
// 2. 判断是否可以hook
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
// 3.1 获取aspect容器
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//3.2 生成aspect标识,每个标识对应一个aspect
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
//3.3 将aspect添加到容器里面
[aspectContainer addAspect:identifier withOptions:options];
// 4.进行hook
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
通过这个入口方法可以了解到添加一个hook的具体过程,具体过程如下:
- 先加锁保证线程安全(这里如果发散的话,其实可以了解一下iOS中的各种锁)。
- 判断是否可以添加hook
- 如果可以hook的话,创建对应的aspect标识,如果标识创建成功,添加到容器,进行下一流程。
- 进行hook
用一张图来表示整个流程的话就如下图所示:
aspect_performLocked
aspect_performLocked
这个方法很简单,就是一个加锁的方法,代码如下:
static void aspect_performLocked(dispatch_block_t block) {
static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&aspect_lock);
block();
OSSpinLockUnlock(&aspect_lock);
}
这里使用了OSSpinLock
,这是个自旋锁,关于锁的知识,这里不展开讲,后面可能会专门写文章去研究锁。这个方法主要保证我们添加aspect时的线程安全。
aspect_isSelectorAllowedAndTrack
aspect_isSelectorAllowedAndTrack
这个方法是检查是否可以添加aspect。代码比较长,这里逐步进行分析。
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;
}
这里创建了一个不允许hook的方法集合,如果要hook的方法在集合中,直接返回NO
,不允许hook。
// Additional checks.
AspectOptions position = options&AspectPositionFilter;
// dealloc 只允许option为before时hook
if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
return NO;
}
这里对hook dealloc
方法做了判断,dealloc
方法只允许在option为AspectPositionBefore
时才允许hook。
if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) { // 查看类中是否有hook的方法
NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
return NO;
}
如果当前类没有响应要hook的方法,直接返回NO
不允许hook。
接下来有个判断,class_isMetaClass(object_getClass(self))
,object_getClass
是获取当前类的isa
指针指向的对象。如果当前类的isa
是元类就会进行下面一系列检查。Objective-C中只有类对象的isa
指向元类。所以如果调用hook方法的是一个实例对象,这里直接返回YES
。如果是类对象这里要进行判断。加这个判断的主要是为了保证在类继承体系上只允许对一个方法进行一次hook。下面来看一下是如何保证的:
Class klass = [self class];
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
Class currentClass = [self class];
这里声明了3个变量,其中klass
和currentClass
都代表当前类。swizzledClassesDict
是一个可变的字典。获取可变字典通过一个函数获取的,代码如下:
static NSMutableDictionary *aspect_getSwizzledClassesDict() {
static NSMutableDictionary *swizzledClassesDict;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
swizzledClassesDict = [NSMutableDictionary new];
});
return swizzledClassesDict;
}
接下来看下面的代码用到了一个类AspectTracker
, 先来看一下AspectTracker
的实现,AspectTracker
的实现很简单,对应的方法在下面代码中已经标明注释
// AspectTracker interface
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
// 当前tracker的类
@property (nonatomic, strong) Class trackedClass;
// 当前tracker对应的类名
@property (nonatomic, readonly) NSString *trackedClassName;
// 当前类hook的方法名
@property (nonatomic, strong) NSMutableSet *selectorNames;
// 记录tracker类子类的tracker,key是selectorName,value是set
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
// 添加对应selectorName方法的子类tracker
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
// 移除对应selectorName方法的子类tracker
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
// 判断子类是否hook了当前方法
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
// 获取当前类对应selectorName的子类tracker
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end
// AspectTracker implementation
@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);
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys];
}
@end
接下来看一下如何使用AspectTracker
的:
AspectTracker *tracker = swizzledClassesDict[currentClass];
// 先检查子类是否Hook
if ([tracker subclassHasHookedSelectorName:selectorName]) {
NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}
先获取当前类的tracker,然后检查当前类的子类是否已经对要进行hook的方法进行了hook,如果已经进行了hook,抛出错误。返回NO
不允许进行hook。如果子类没有进行hook,继续执行以下代码:
// 检查父类是否hook
do {
tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) {
if (klass == currentClass) {
// Already modified and topmost!
return YES;
}
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}
} while ((currentClass = class_getSuperclass(currentClass))); //遍历所有的父类,直到为nil
这个方法是从自己开始往父类进行遍历,看看是否进行hook,如果当前类已经hook,返回YES
,允许添加hook,如果是父类已经Hook,抛出错误,返回NO
,不允许hook。
如果代码可以走到下面说明当前类的继承体系上没有类对要进行hook的selector进行过hook,最后肯定返回YES
,只是在返回YES
前需要进行把给当前类添加tracker,并且在父类继承体系上面打标记,标记这个方法已经有类hook过了,当用继承体系其他类进行hook时,在前面两步就可以拦住。
// 添加Hook的tracker,当前类和父类继承体系里面的都会添加。hook一个方法只会有一次这里。只有再次hook不同方法时才会再次走这里。
// 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)));
总结
aspect_isSelectorAllowedAndTrack
这个方法用来判断是否可以为当前类的某个方法添加aspect,主要会进行以下几步验证:
- 黑名单验证
- dealloc不允许在option为非
AspectPositionBefore
的情况下hook - 当前类没有响应该方法
- 继承体系上面不允许多个类进行hook
进行Hook
如果允许hook,下面进行hook流程。
AspectsContainer
前面代码3.1是先获取aspectContainer,代码比较简单,这里就不进行解释了
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
// aspect_getContainerForObject
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
NSCParameterAssert(self);
SEL aliasSelector = aspect_aliasForSelector(selector);
AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
if (!aspectContainer) {
aspectContainer = [AspectsContainer new];
objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
}
return aspectContainer;
}
// aspect_aliasForSelector
static SEL aspect_aliasForSelector(SEL selector) {
NSCParameterAssert(selector);
return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
}
// AspectsMessagePrefix
static NSString *const AspectsMessagePrefix = @"aspects_";
AspectIdentifier
AspectIdentifier
表示一个aspect,每添加一个aspect,就会生成一个AspectIdentifier
放到AspectsContainer
中。接下来看一下AspectIdentifier
的定义:
// Tracks a single aspect.
@interface AspectIdentifier : NSObject<AspectToken>
// 初始化方法
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
// invoke aspect
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
// hook的方法
@property (nonatomic, assign) SEL selector;
// 要执行的block
@property (nonatomic, strong) id block;
// block的方法签名
@property (nonatomic, strong) NSMethodSignature *blockSignature;
// hook的对象
@property (nonatomic, weak) id object;
// hook的option
@property (nonatomic, assign) AspectOptions options;
@end
从AspectIdentifier
的定义可以看出来,它和添加一个aspect的参数几乎是一一对应的,每添加一个aspect,都会生成一个对应的AspectIdentifier
保存起来,等执行到对应的方法时,取出对应的aspect调用一下。接下来看一下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;
}
这里有两个地方需要重点讲一下,一个是获取block的方法签名,另外一个是校验block签名是否和原方法签名是否兼容。这两个地方看明白会有利于了解Objective-C的本质。
aspect_blockMethodSignature
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
// 找到desc中的方法签名,生成签名
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;
desc += 2 * sizeof(unsigned long int);
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
类型,从这里可以看出,Objective-C中的block其实就是一个对象,接下来看一下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;
有没有很眼熟,这不就是block的定义么,关于block,后面写一篇专门的文章来讲。这里可以记住,一个block类型在Objective-C中是这样存储的。把传进来的block强制转换成AspectBlockRef
类型,是为了取到其中某个变量的值。
接下来看一下如何取出block的方法签名:
if (!(layout->flags & AspectBlockFlagsHasSignature)) {
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
先根据标志位判断当前block是否有签名,如果没有签名,直接报错。返回nil,这里返回nil会打断整个hook流程,也就是要求传进来的block必须是有签名的。如果是有签名的block,接下来就是取签名的过程:
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
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中的descriptor
指针,descriptor
指针向下偏移两个unsigned long int
的位置就是copy
函数的地址,接下来判断根据flag判断是否包含copy
和dispose
函数地址,如果包含的话,继续往下偏移两个void *
的大小。这时指针肯定移动到了const char *signature
的地址,如果desc不存在说明block没有类型签名,也会报错,终止hook。desc有值的话,说明有方法签名,最后返回方法签名。
可以通过调试看一下具体的方法签名,通过如下代码进行调试:
[UIViewController aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:(AspectPositionBefore | AspectPositionInstead) usingBlock:^(id<AspectInfo> info){
NSLog(@"TestOne hook");
} error:NULL];
得到的方法签名如下:
(const char *) signature = 0x000000010d6379e7 "v20@?0@\"<AspectInfo>\"8B16"
想要看懂这串编码需要了解一些Objective-C中type encodings的知识。在Objective-C中,编译器将每个方法的返回值和参数类型编码为一个字符串,并将这个字符串与方法的selector关联在一起。可以通过@encode
指令来获取类型编码。在Objective-C Runtime Programming Guide中的Type Encodings中列举了Objective-C中所有的类型编码:
需要注意的是Objective-C不支持
long double
类型,@encode(long double)
会返回d
,和double
的类型编码一样。
了解了这些再来看前面block的签名字符串就能看懂了,"v20@?0@\"<AspectInfo>\"8B16"
,v
代表返回值类型是void
,20代表整个函数的偏移,@?
表示block类型,0代表偏移量,@\"<AspectInfo>\"
表示第一个参数是遵守AspectInfo
协议的对象,8代表偏移量,B
表示第二个参数BOOL类型,16是偏移量。
接下来打印一下方法签名, 可以看到type encoding和offset和上面的字符串是对应的(除了v
的不对应,字符串中v
后面的数字应该表示整个函数的偏移,不是相对偏移)。
<NSMethodSignature: 0x600000a902a0>
number of arguments = 3
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (@) '@"<AspectInfo>"'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
conforms to protocol 'AspectInfo'
argument 2: -------- -------- -------- --------
type encoding (B) 'B'
flags {}
modifiers {}
frame {offset = 16, offset adjust = 0, size = 8, size adjust = -7}
memory {offset = 0, size = 1}
aspect_isCompatibleBlockSignature
aspect_isCompatibleBlockSignature
这个函数是校验添加的aspect和原来的方法签名是否相同。如果不同,不允许添加aspect。下面来看一下实现:
static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
NSCParameterAssert(blockSignature);
NSCParameterAssert(object);
NSCParameterAssert(selector);
BOOL signaturesMatch = YES;
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
if (blockSignature.numberOfArguments > 1) {
const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
if (blockType[0] != '@') {
signaturesMatch = NO;
}
}
// Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
// The block can have less arguments than the method, that's ok.
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:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
AspectError(AspectErrorIncompatibleBlockSignature, description);
return NO;
}
return YES;
}
该方法,先比较参数个数是否相等,如果不相等直接返回NO
,终止hook。接下来再判断第二个参数是否是一个对象,如果不是,直接返回NO
。接下来逐个比较参数,看参数类型是否一样,只有所有参数类型一样,才会返回YES
,这里需要解释一下比较参数为什么从2开始比较。其实注释已经解释的很清楚了,Objective-C方法中有两个隐藏函数,第一个是self
,第二个是_cmd
,所以比较传入参数,需要从2开始比较。
接下来带代码就非常简单了,把传入的参数赋值给自己的属性。
aspect_prepareClassAndHookSelector
前面的AspectIdentifier
如果可以初始化成功,就会进行hook。aspect_prepareClassAndHookSelector
是hook的核心。接下来看一下这个方法的实现:
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
Class klass = aspect_hookClass(self, error); // hook forwardInvocation
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) { // hook 对应的方法
// Make a method alias for the existing method implementation, it not already copied.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
}
// We use forwardInvocation to hook in.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
这个方法体现了Aspects
这个库的hook思想,先来简单总结一下。
Aspects
是先把类的forwardInvocation:
方法进行hook。- 如果想要对类的某个方法进行Hook,
Aspects
会把想要hook的方法实现指向_objc_msgForward
,_objc_msgForward
这个函数是一个汇编标记,标记这个方法需要走消息转发流程。关于这块不明白的可以去看一下Objective-C的消息转发流程。当调用已经hook过的方法时,就会走转发流程,因为Aspects
已经hook了forwardInvocation:
所以就会走Aspects
自己的实现,然后Aspects
在自己实现里面做一些实现。
aspect_hookClass
这个方法是用来hook类的forwardInvocation:
方法,下面来看一下如何hook类的forwardInvocation:
方法。
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
statedClass
和baseClass
是有区别的,对于实力对象而言,这两个意义一样。但是对于类对象而言,statedClass
表示自己,baseClass
表示元类。
// Already subclassed
// static NSString *const AspectsSubclassSuffix = @"_Aspects_";
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.
// baseClass不是元类的话,那么self就是对象,statedClass 和baseClass肯定相等,不相等的话是因为KVO产生了中间类,这种情况对KVO的中间类调用aspect_swizzleClassInPlace
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
如果类名里面已经包含了AspectsSubclassSuffix
这个字符串说明已经hook过了。直接返回baseClass。
如果baseClass
是元类,说明self
是类对象,直接进行hook。aspect_swizzleClassInPlace
这个函数作用就是hook类的forwardInvocation:
方法。
如果baseClass
不是元类的话,那么self
就是实例对象,statedClass
和baseClass
肯定是指向同一个地址。不想等的情况只能是KVO产生了中间类。这种情况直接对中间类就行Hook。中间类就是baseClass
。
如果这几个判断条件都没有走,说明self是实例对象,并且没有被KVO过。那么走下面的方法:
// 拼接"_Aspects_"字符串
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;
}
// swizzle 子类的forwardInvocation
aspect_swizzleForwardInvocation(subclass);
// hook实例class方法
aspect_hookedGetClass(subclass, statedClass);
// hook类class方法
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
// 这里偷偷换了一下,class方法返回statedClass,实际上object_getClass返回新创建的类
// 把当前对象的isa指向自己。
object_setClass(self, subclass);
return subclass;
到这里我们可以看到,如果对某个对象的方法进行hook,Aspects
会创建一个对象的子类,在子类的基础上进行操作。这样的好处是不需要修改对象本身的类。当remove aspects的时候,如果发现当前对象的所有aspect都移除了,可以直接将isa指针指向回原来的类。
接下来看一下Aspects
如何创建子类进行hook的。新建类的名字,会先加上AspectsSubclassSuffix
后缀,标记成子类。然后调用objc_getClass
方法,去查找是否已经创建过子类。能来到这里肯定是还没有创建,所以返回的肯定是nil。接下来就会调用objc_allocateClassPair方法创建子类。如果创建失败会报错。终止hook流程。创建成功的话,会走下面的流程。调用aspect_swizzleForwardInvocation
hook新建子类的forwardInvocation:
方法,调用aspect_hookedGetClass
hook子类类的class
方法,返回statedClass
,statedClass
就是原来实例的类对象。调用aspect_hookedGetClass
hook元类的class
方法,返回statedClass
。接下来调用objc_registerClassPair
方法注册新建的子类。最后把原来实例的isa指向新创建的子类。
aspect_swizzleForwardInvocation
前面aspect_hookClass
方法中用到了aspect_swizzleForwardInvocation
和aspect_swizzleClassInPlace
方法。aspect_swizzleClassInPlace
最后也是调用了aspect_swizzleForwardInvocation
,只是多了一个加锁的过程。现在来看一下aspect_swizzleForwardInvocation
方法实现:
static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
// swizzling 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@:@");
// 如果以前类里面已经实现了forwardInvocation,用另一个sel挂住原来的
if (originalImplementation) {
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
直接调用class_replaceMethod
方法,替换类的forwardInvocation:
方法,替换的实现是__ASPECTS_ARE_BEING_CALLED__
,这个实现后面分析。如果原来的类实现了forwardInvocation:
,用AspectsForwardInvocationSelectorName
这个selector指向原来的实现。
__ASPECTS_ARE_BEING_CALLED__
接下来看一下__ASPECTS_ARE_BEING_CALLED__
的实现:
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
// 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 *objectContainer = objc_getAssociatedObject(self, aliasSelector);
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)
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)];
}
前面把原来类forwardInvocation:
的实现替换成__ASPECTS_ARE_BEING_CALLED__
这个函数。所以当调用类hook方法时,会走到这个函数中。
SEL originalSelector = invocation.selector;
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
invocation.selector = aliasSelector;
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
这几行代码做了以下几件事:
- 获取原始的selector
- 获取带有
aspects_
前缀的selector - 替换selector
- 获取实例对象的aspectsContainer
- 获取类的aspectContainer
- 初始化
AspectInfo
,传入instance和invocation
接下来调用aspect_invoke
宏定义,执行需要再before调用的aspect。宏定义里面做了两件事,一个是执行了[aspect invokeWithInfo:info]
方法,一个是把需要remove的aspect添加到等待移除的数组。
接下来看一下[aspect invokeWithInfo:info]
的实现:
- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
NSInvocation *originalInvocation = info.originalInvocation;
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
// paranoid: 偏执
// 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;
}
// 0: block
// 1:
// The `self` of the block will be the AspectInfo. Optional.
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
// 拷贝参数
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;
}
这个方法中主要做了4件事:
- 根据
blockSignature
初始化invocation
- 把
info
放到参数的第二个位置 - 把原来
invocation
中的参数拷贝到block的invocation
中 - 调用添加的aspect
所以只要调用aspect_invoke
就能调用我们添加的aspect。对应的传入beforeAspects、
insteadAspects、
afterAspects就能实现
before、
instead、
after`的hook。
// 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)));
}
如果存在instead的aspect,直接调用,不存在的话,调用原来实现。这里需要注意,原来的实现绑定在aliasSelector
上面。
// After hooks.
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
这两行代码是调用After Aspect,原理和前面一样。
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
如果原来的实现没有正确执行,调用原来的forwardInvocation:
方法。如果原来的类没有实现forwardInvocation:
方法,就会报错崩溃了。
总结
aspect_hookClass
到这里就讲完了,这个方法的目的就是hook对应类的forward Invocation:
方法。在里面进行了各种判断,下面总结一下判断的情况:
- 如果已经hook过了直接返回
- 如果当前类是类对象,直接进行hook
- 如果是KVO的情况,hook KVO产生的子类对象
- 如果是对象,动态创建子类,hook子类的
forwardInvocation:
方法
给对应的方法添加Aspect
通过aspect_hookClass
方法对forwardInvocation:
进行hook之后,会返回hook完后的类。接下来只需要把需要添加aspect的selector指向forwardInvocation:
即可。下面看一下Aspects
如何做的:
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
这两句代码是获取要hook的方法原来的实现。
if (!aspect_isMsgForwardIMP(targetMethodIMP)) { // swizzling method
// Make a method alias for the existing method implementation, it not already copied.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
}
// We use forwardInvocation to hook in.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
static BOOL aspect_isMsgForwardIMP(IMP impl) {
return impl == _objc_msgForward
#if !defined(__arm64__)
|| impl == (IMP)_objc_msgForward_stret
#endif
;
}
aspect_isMsgForwardIMP
这个函数用来判断原来的实现是否指向_objc_msgForward
或者_objc_msgForward_stret
。这两个IMP
是消息转发的标记。这里是判断当前的IMP
是否为消息转发。
如果不是消息转发,就获取当前原始的selector对应IMP
的方法编码,如果当前类不能响应aspect_xxx
这个方法,就给当前类添加aspect_xxx
selector,实现为原来方法的实现。
然后调用class_replaceMethod
把原来的selector指向_objc_msgForward
或者_objc_msgForward_stret
。这样,当原来的方法被调用时,会直接走消息转发,因为前面已经hook了forwardInvocation:
,所以会走到自己的处理逻辑中。
到这里,就完成了一次完整的hook。
aspect_remove
aspect_remove
是移除一个hook。接下来看一下如何移除:
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;
}
aspect_performLocked
是保证线程安全。先把aspect从aspectContainer中移除。然后调用aspect_cleanupHookedClassAndSelector
取消对当前方法的hook。
接下来看一下aspect_cleanupHookedClassAndSelector
的实现:
Class klass = object_getClass(self);
BOOL isMetaClass = class_isMetaClass(klass);
if (isMetaClass) {
klass = (Class)self;
}
这几句代码的作用是取到类对象。
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (aspect_isMsgForwardIMP(targetMethodIMP)) {
// Restore the original method implementation.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
IMP originalIMP = method_getImplementation(originalMethod);
NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
class_replaceMethod(klass, selector, originalIMP, typeEncoding);
AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
这里检查当前的方法是否被标记成消息转发,如果标记成了消息转发,换回原来的方法实现。经过前面的代码,这些方法应该很容易看明白。这里有个问题需要注意,如果一个类的两个对象同时对类的某个方法进行hook,其中某个对象把方法还原后,另外一个对象的方法也还原了,这里是对整个类进行的操作。
aspect_deregisterTrackedSelector(self, selector);
static void aspect_deregisterTrackedSelector(id self, SEL selector) {
if (!class_isMetaClass(object_getClass(self))) return;
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
NSString *selectorName = NSStringFromSelector(selector);
Class currentClass = [self class];
AspectTracker *subclassTracker = nil;
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (subclassTracker) {
[tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName];
} else {
[tracker.selectorNames removeObject:selectorName];
}
if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) {
[swizzledClassesDict removeObjectForKey:currentClass];
}
subclassTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
}
aspect_deregisterTrackedSelector
方法的作用是取消对该方法的tracker。继承体系中的tracker也取消。如果当前类没有进行过hook了,把当前类从swizzledClassesDict
中移除。
// Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
AspectsContainer *container = aspect_getContainerForObject(self, selector);
if (!container.hasAspects) {
// Destroy the container
aspect_destroyContainerForObject(self, selector);
// Figure out how the class was modified to undo the changes.
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));
// We can only dispose the class pair if we can ensure that no instances exist using our subclass.
// Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
//objc_disposeClassPair(object.class);
}else {
// Class is most likely swizzled in place. Undo that.
if (isMetaClass) {
aspect_undoSwizzleClassInPlace((Class)self);
}else if (self.class != klass) {
aspect_undoSwizzleClassInPlace(klass);
}
}
}
如果container中已经没有aspect了,移除当前类的关联对象。如果当前类里面包含_Aspects_
后缀,说明创建了子类,需要把当前类的isa指针指回原来的类。
aspect_undoSwizzleClassInPlace
里面会调用aspect_undoSwizzleForwardInvocation
,aspect_undoSwizzleClassInPlace
里面只是进行了加锁,并且把当前class移除了swizzledClasses
容器。接下来看一下aspect_undoSwizzleForwardInvocation
实现:
static void aspect_undoSwizzleForwardInvocation(Class klass) {
NSCParameterAssert(klass);
Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
// There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
}
这个方法的作用就是还原forwardInvocation:
实现。
Aspects的坑
Aspects
设计虽然很好,但是在项目里面用还是有一些坑的,比如我们在项目里面手动hook了一个方法,然后用Aspects
也进行了hook,就有可能会崩溃(先用Aspects去hook,再手动hook就会崩溃),比如下面代码:
@implementation UIViewController (TestOne)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
先在一个分类中hookviewWillAppear:
方法,然后在另一个分类中用Aspects
也hookviewWillAppear:
方法:
@implementation UIViewController (TestTwo)
+ (void)load {
[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info , BOOL animated) {
NSLog(@"viewWillAppear");
} error:nil];
}
@end
运行就会崩溃。即便能保证项目里面不手动hook,也不能保证一些第三方sdk不会手动进行hook。如果想要保证能正常使用,就需要对Aspects
库进行改造。这个不在本文讨论范围,暂时不发散了。解决办法可以在参考链接中找到。