1.Aspects是什么?
开源的库,面向切面编程,它允许你在每一个类和每一个实例中存在的方法里面加入任何代码。可以在方法执行之前或者之后执行,也可以替换掉原有的方法。通过Runtime消息转发实现Hook,其实就是Runtime的封装,使用方式更加方便。
2.关键代码及分析
+ (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);
}
hook 方法:aspect_hookSelector
,传入
- SEL(要Hook的方法),
- options(远方法调用调用之前或之后调用或者是替换)
- block(要执行的代码),
- error(错误信息)
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(^{
//先判断参数的合法性,如果不合法直接返回nil
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
//参数合法
//创建容器
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//创建一个AspectIdentifier对象(保存hook内容)
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
//把identifier添加到容器中(根据options,添加到不同集合中)
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
- 判断上面传入的方法的合法性
- 如果合法就创建
AspectsContainer
容器类,这个容器会根据传入的切片时机进行分类,添加到对应的集合中去 - 创建
AspectIdentifier
对象保存hook内容 - 如果
AspectIdentifier
对象创建成功,就把AspectIdentifier
根据options添加到对应的数组中 - 最终调用
aspect_prepareClassAndHookSelector(self, selector, error);
开始进行hook
3.Aspects是怎么对类和方法进行Hook的?
先对class进行hook再对selector进行hook
Hook Class
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
//获取类
Class statedClass = self.class;
//获取类的isa指针
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// Already subclassed
//判断是否包含_Aspects_,如果包含,就说明被hook过了,
//如果不包含_Aspects_,再判断是不是元类,如果是元类调用aspect_swizzleClassInPlace
//如果不包含_Aspects_,也不是元类,再判断statedClass和baseClass是否相等,如果不相等,说明是被kvo过的对象因为kvo对象的isa指针指向了另一个中间类,调用aspect_swizzleClassInPlace
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.
//如果不是元类,也不是被kvo过的类,也没有被hook过,就继续往下执行,创建一个子类,
//拼接类名为XXX_Aspects_
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
//根据拼接的类名获取类
Class subclass = objc_getClass(subclassName);
//如果上面获取到的了为nil
if (subclass == nil) {
//baseClass = MainViewController,创建一个子类MainViewController_Aspects_
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);
//把subclass的isa指向了statedClass
aspect_hookedGetClass(subclass, statedClass);
//subclass的元类的isa,也指向了statedClass。
aspect_hookedGetClass(object_getClass(subclass), statedClass);
//注册刚刚新建的子类subclass,再调用object_setClass(self, subclass);把当前self的isa指向子类subclass
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
Hook Method
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
Class klass = aspect_hookClass(self, error);
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
// Make a method alias for the existing method implementation, it not already copied.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
//子类里面不能响应aspects_xxxx,就为klass添加aspects_xxxx方法,方法的实现为原生方法的实现
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));
}
}
对selector进行hook,首先获取到原来的方法,然后判断是不是指向了_objc_msgForward,
没有的话,就获取原来方法的方法编码,为新建的子类添加一个方法aspects__xxxxx,
并将新建方法的IMP指向原来方法,再把原来类的方法的IMP指向_objc_msgForward,
hook完毕
4.ASPECTS_ARE_BEING_CALLED
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
//获取原始的selector
SEL originalSelector = invocation.selector;
//获取带有aspects_xxxx前缀的方法
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
//替换selector
invocation.selector = aliasSelector;
//获取实例对象的容器objectContainer,这里是之前aspect_add关联过的对象
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
//获取获得类对象容器classContainer
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
//初始化AspectInfo,传入self、invocation参数
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// Before hooks.
//调用宏定义执行Aspects切片功能
//宏定义里面就做了两件事情,一个是执行了[aspect invokeWithInfo:info]方法,一个是把需要remove的Aspects加入等待被移除的数组中。
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)];
}
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
- (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.
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
void *argBuf = NULL;
//把originalInvocation中的参数
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;
}
获取数据传递到aspect_invoke
里面,调用invokeWithInfo
,执行切面代码块,执行完代码块以后,获取到新创建的类,判断是否可以响应aspects__xxxx
方法,现在aspects__xxxx
方法指向的是原来方法实现的IMP,如果可以响应,就通过[invocation invoke];
调用这个方法,如果不能响应就调用__aspects_forwardInvocation:
这个方法,这个方法在hookClass的时候提到了,它的IMP指针指向了原来类中的forwardInvocation:
实现,可以响应就去执行,不能响应就抛出异常doesNotRecognizeSelector
;
5.移除Aspects
- (BOOL)remove {
return aspect_remove(self, NULL);
}
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;
}
调用remove方法,然后清空AspectsContainer
里面的数据,调用aspect_cleanupHookedClassAndSelector
清除更多的数据