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实例的数组属性(beforeAspects、insteadAspects、afterAspects)的特性为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)];
}
整个流程主要是:
- beforeBlock执行(如果有) -> 原始方法执行 -> afterBlock执行(如果有) -> 清除方法的hook状态(如果有)
- 原始方法实现如果不存在:如果原始类实现了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]; \
} \
}
此函数的作用是:
- 依次使用AspectIdentifier对象进行block调用,传入AspectInfo信息。
- 如果执行后需要移除此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));
}
优秀代码解读: