Aspects源码浅析
同步发布到博客地址Aspects源码浅析
Aspects 可以很方便的让我们 hook 要执行的方法,可以很方便的在方法执行前,执行后来执行我们的操作,也可以替换原方法的实现。
Delightful, simple library for aspect oriented programming in Objective-C and Swift.
实现原理简单的说就是,通过动态创建子类、动态修改子类方法的实现,将原类的类型指向子类,从而将原来的方法的实现指向了子类的方法实现,可以方便的观察方法的执行情况。跟系统实现 KVO 的原理一样。
比如 hook A 类的 xxx 方法,实现原理大致就是:
- 动态创建类
A
的子类B
(A__Aspects_
) -
B
动态添加方法aspects_xxx
方法,aspects_xxx
的实现指向原来的xxx
方法 -
B
替换forwardInvocation:
实现为__ASPECTS_ARE_BEING_CALLED__
。 -
B
替换 原有的xxx
实现为_objc_msgForward
- 将
A
的类型设置为B
。 - 当调用
xxx
时,这个时候回A
的类型已经被设置为B
,会向B
的方法列表里面查找xxx
的实现。这个时候xxx
在B
中已经被替换为_objc_msgForward
,进入消息转发流程,forwardInvocation:
,forwardInvocation:
的实现被替换为__ASPECTS_ARE_BEING_CALLED__
。最终会进入__ASPECTS_ARE_BEING_CALLED__
来处理。
关于消息转发的流程我这边在这里有一个简单的总结:Objective-C 中的消息与消息转发
简单使用
Aspects
的 API 封装的非常简洁,对外暴露了两个方法。
+ (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.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
分别 hook 实例方法和类方法。
使用起来非常简单
[testController aspect_hookSelector:@selector(viewWillDisappear:) withOptions:0 usingBlock:^(id<AspectInfo> info, BOOL animated) {
UIViewController *controller = [info instance];
if (controller.isBeingDismissed || controller.isMovingFromParentViewController) {
[[[UIAlertView alloc] initWithTitle:@"Popped" message:@"Hello from Aspects" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil] show];
}
} error:NULL];
源码分析
Aspects
hook 方法最终都会进入一个叫做 aspect_add
的方法。
aspect_add
大体做的事情有三件。
-
aspect_performLocked
加锁保证线程安全。 -
aspect_isSelectorAllowedAndTrack
判断是否允许 hook -
aspect_prepareClassAndHookSelector
动态创建原有类的子类和替换原有方法实现来达到 hook 的目的。
aspect_performLocked
就略去不谈,我们主要分析下下面两个方法的实现。
aspect_isSelectorAllowedAndTrack
1. 判断 hook 的方法是否为系统方法。
static dispatch_once_t pred;
dispatch_once(&pred, ^{
disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
});
// Check against the blacklist.
NSString *selectorName = NSStringFromSelector(selector);
//判断是否是上面的几个方法,如果是,不跟踪。retain、release 等
if ([disallowedSelectorList containsObject:selectorName]) {
NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
AspectError(AspectErrorSelectorBlacklisted, errorDescription);
return NO;
}
判断是否是 @"retain"
, @"release"
, @"autorelease"
, @"forwardInvocation:"
方法,如果是这几个方法,返回 NO
,不进行 hook。
2. 判断 hook 类型。
通过位运算快速计算出 hook 类型。
AspectOptions position = options&AspectPositionFilter;
#define AspectPositionFilter 0x07
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.
};
AspectPositionFilter == 0x07 转为为二进制位 0000 0000 0000 0111
AspectOptions 选项值分别为 0、1、2、8,
AspectOptionAutomaticRemoval = 1 << 3 就是1 的二进制左移3位 变成 8
这样可以快速的计算出当前选项值。
转为二进制
AspectPositionAfter 0000 0000 0000 0000
AspectPositionInstead 0000 0000 0000 0001
AspectPositionBefore 0000 0000 0000 0010
AspectOptionAutomaticRemoval 0000 0000 0000 1000
上面这个几个值 分别于 AspectPositionFilter 进行 & 位运算后等到值
AspectPositionAfter &AspectPositionFilter 0000 0000 0000 0000
AspectPositionInstead &AspectPositionFilter 0000 0000 0000 0001
AspectPositionBefore &AspectPositionFilter 0000 0000 0000 0010
AspectOptionAutomaticRemoval &AspectPositionFilter 0000 0000 0000 0000
if (aspect.options & AspectOptionAutomaticRemoval) 可以计算出 这里是为了计算出那些是运行后就移除的。
AspectPositionAfter &AspectPositionFilter 0000 0000 0000 0000
AspectPositionInstead &AspectPositionFilter 0000 0000 0000 0000
AspectPositionBefore &AspectPositionFilter 0000 0000 0000 0000
AspectOptionAutomaticRemoval &AspectOptionAutomaticRemoval 0000 0000 0000 1000
3. 如果要 hook dealloc
方法。
如果要 hook dealloc
方法,只能在方法执行前 hook。
if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
return NO;
}
4. 判断当前类是否相应要 hook 的方法
判断当前类是否相应要 hook 的方法,如果不响应,返回 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;
}
5. 判断是否为类对象
如果不是类对象,就是实例对象,直接 返回 YES
。
class_isMetaClass(object_getClass(self))
class_isMetaClass 判断是不是 MetaClass
object_getClass 当参数传入的是一个实例对象的时候,返回类对象,当参数传入的是一个类对象的时候,返回一个元类对象(MetaClass)
class_isMetaClass
Class cla1 = object_getClass([UIView new]);
Class cla2 = object_getClass([UIView class]);
NSLog(@" %@ %d",cla1,class_isMetaClass(cla1));//UIView 0
NSLog(@" %@ %d",cla2,class_isMetaClass(cla2));//UIView 1
class_isMetaClass(object_getClass(self) 就是判断 self 是不是类对象,如果是类对象进入 分支,如果是实例对象,返回 YES
如果是类对象,进入分支。
6.判断子类是否已经 hook 改方法
从全局的字典里面取出当前类相关联的 AspectTracker
,判断子类是否已经 hook 过改方法。
if (class_isMetaClass(object_getClass(self))) {
Class klass = [self class];
//全局缓存的字典 key是class;value是AspectTracker实例
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
Class currentClass = [self class];
AspectTracker *tracker = swizzledClassesDict[currentClass];
//判断子类是否已经 hook,如果子类已经 hook ,提示子类已经 hook了,返回 NO
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;
}
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
return self.selectorNamesToSubclassTrackers[selectorName] != nil;
}
7. 判断父类是否已经 hook 改方法
do {
//获取 AspectTracker
tracker = swizzledClassesDict[currentClass];
//判断 AspectTracker 已经 hook 的方法是否包含要 hook 的方法名
if ([tracker.selectorNames containsObject:selectorName]) {
//如果是当前类,返回 YES ,可以对当前类再次进行 hook。
if (klass == currentClass) {
// Already modified and topmost!
return YES;
}
//不一致的话,返回 NO,报错已经 在 父类 HOOK 过了,保证一个继承链上只能 hook 一个类
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)));
判断继承链上向父类查找是否已经有类 hook 这个方法了。
Class currentClass = object_getClass([UITableViewCell class]);
NSLog(@"currentClass == %@",currentClass);//UITableViewCell
do {
NSLog(@"class_getSuperclass == %@",currentClass);
//UITableViewCell
//UIView
//UIResponder
//NSObject
//NSObject
} while ((currentClass = class_getSuperclass(currentClass)));
当已经找到继承链的顶端后,class_getSuperclass 获取的数据为 null ,跳出循环。
8. 在当前类的继承链上标识 hook 关系。
如果当前类的继承链上都没有 hook 过改方法,在当前类的继承链上标识 hook 关系。
do {
//从全局缓存的字典中获取 AspectTracker
tracker = swizzledClassesDict[currentClass];
//如果 AspectTracker 为空
if (!tracker) {
// 根据 currentClass 生成 AspectTracker 实例,并缓存在全局的字典里
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
if (subclassTracker) {
//如果 subclassTracker 存在 tracker 的根据 selectorName 存储的 set 中,添加subclassTracker
[tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
} else {
// 把 selectorName 放入已经 hook 的集合里面
[tracker.selectorNames addObject:selectorName];
}
// All superclasses get marked as having a subclass that is modified.
subclassTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
aspect_prepareClassAndHookSelector
aspect_prepareClassAndHookSelector
主要做了三件事情。
-
aspect_hookClass
动态的创建 当前类的子类,并交换forwardInvocation:
、class
方法的实现 -
aspect_isMsgForwardIMP
,判断当前调用的方法是否是_objc_msgForward
的实现,如果是,跳过。 - 态的替换原方法的实现
class_addMethod
。class_replaceMethod
动。
1. aspect_hookClass
动态的创建当前类的子类,并返回创建的子类,并交换 forwardInvocation
、class
方法的实现。
1. 判断是否已经hook过
判断当前类是否已经被 hook 过
if ([className hasSuffix:AspectsSubclassSuffix]) {
如果已经hook 过,直接返回 object_getClass(self)
获取的类型,也就是动态创建的子类。
2. 判断是否是类对象
如果是类对象,aspect_swizzleForwardInvocation
替换 forwardInvocation
的实现为 __ASPECTS_ARE_BEING_CALLED__
3. 判断当前类名称是否一致
如果是self.class
和 object_getClass(self)
获取的类型不一致,有可能进行了 KVO 操作,aspect_swizzleForwardInvocation
替换 forwardInvocation
的实现为 __ASPECTS_ARE_BEING_CALLED__
4. 动态创建当前类的子类
如果上面的判断都不成立,就是一个实例对象,且没有被KVO。动态创建子类,原有类后面拼接 AspectsSubclassSuffix
作为新的类名,并且替换子类的 forwardInvocation:
、-(Class)class
、+(Class)class
方法。
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
//创建 subclassName 的类,他的父类为 baseClass(原来的类)
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);
// 交换实例方法 class 方法的实现
aspect_hookedGetClass(subclass, statedClass);
// 交换类方法 class 方法的实现
aspect_hookedGetClass(object_getClass(subclass), statedClass);
//注册子类
objc_registerClassPair(subclass);
aspect_swizzleForwardInvocation
为新创建的子类替换 forwardInvocation:
实现,方法的实现指向 __ASPECTS_ARE_BEING_CALLED__
。添加 __aspects_forwardInvocation:
方法,方法的实现指向 __ASPECTS_ARE_BEING_CALLED__
,
static void aspect_swizzleForwardInvocation(Class klass) {
NSCParameterAssert(klass);
// If there is no method, replace will act like class_addMethod.
//替换 forwardInvocation 方法是实现
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
//将方法的实现 originalImplementation 对应的SEL设置为 AspectsForwardInvocationSelectorName
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
aspect_hookedGetClass
修改子类的 -(Class)class
、+(Class)class
方法,使其返回父类的类型。
static void aspect_hookedGetClass(Class class, Class statedClass) {
NSCParameterAssert(class);
NSCParameterAssert(statedClass);
//获取 class 方法的实现
Method method = class_getInstanceMethod(class, @selector(class));
IMP newIMP = imp_implementationWithBlock(^(id self) {
return statedClass;
});
//替换class 方法的实现为 newIMP,返回 statedClass(父类) 的类型
class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
}
objc_registerClassPair
注册子类
5. 设置原有对象类型
把 self
的类型指向 动态创建的子类。
object_setClass(self, subclass);
2. aspect_isMsgForwardIMP
判断当前要 hook 的方法是否是 _objc_msgForward
或者 _objc_msgForward_stret
static BOOL aspect_isMsgForwardIMP(IMP impl) {
return impl == _objc_msgForward
#if !defined(__arm64__)
|| impl == (IMP)_objc_msgForward_stret
#endif
;
}
3. 动态添加方法并且替换原方法的实现
动态添加方法并且替换原方法的实现
1. 获取原方法签名,为原方法添加前缀
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
static SEL aspect_aliasForSelector(SEL selector) {
NSCParameterAssert(selector);
return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
}
2. 动态添加方法
判断当前子类不响应新拼接的方法名。添加新创建的方法名到新类方法列表中。方法的实现指向原有的方法。
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
3.将原有放方法实现替换
将原有的方法实现 替换为 _objc_msgForward
。每次调用 这个方法,就进行了消息转发,相当用调用 forwardInvocation:
,上面 forwardInvocation:
已经被替换为 __ASPECTS_ARE_BEING_CALLED__
方法.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
__ASPECTS_ARE_BEING_CALLED__
最终hook的方法会进入这个方法中。
1. 获取要调用方法名
//获取调用方法名
SEL originalSelector = invocation.selector;
//获取 交换后 的方法名
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
2. 通过方法名获取要调用的方法的集合
//获取 实例消息发送者的集合
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
//获取 类方法消息发送者 的集合
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
3. 按顺序执行回调和方法
- 先便利
AspectsContainer
存储的beforeAspects
数组,执行里面存储的block
; - 然后便利
insteadAspects
数组,执行里面存储的block
; - 如果
insteadAspects
数组为空,执行aliasSelector
方法。aliasSelector
这个时候指向了原来方法的实现; - 然后便利
afterAspects
数组,执行里面存储的block
; -
block
执行的过程中,会判断类型是否为AspectOptionAutomaticRemoval
,如果是,将改选项加入到一个数组中,在方法执行后,解除 hook 关系; - 如果没有找到
aliasSelector
,通过doesNotRecognizeSelector
抛出一个异常。
到这里,大致过了一遍 Aspects
的代码,但是这仅仅是粗略的过了一遍,还有很多细节可以继续扣下去。这里做个记录。