目录
2.3 创建AspectIdentifier对象保存hook内容
2.4 把AspectIdentifier根据options添加到对应的数组中
上次聊到了Aspects的基本使用,这次我们来聊一下Aspects的基本实现原理.
Aspects对外提供了两个接口,而这两个接口都调用了同一个实现aspect_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(^{
//判断是否可以进行方法hook
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
//创建用于存储hook对象的容器对象
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
//根据option类型不同添加到不同的容器中去
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
主要分为以下几个执行步骤:
- 检查必须参数是否为空,并检查传入的相关类和方法是否符合追踪的条件;
- 创建自旋锁来保证线程执行安全;
- 如果参数符合追踪的条件,创建
AspectsContainer
容器类,这个容器会根据传入的切片时机进行分类,添加到对应的集合中去; - 如果创建AspectIdentifier成功,就把创建好的AspectIdentifier对象根据不同的option保存到对应的集合中;
- 使用aspect_prepareClassAndHookSelector进行方法hook.
2.1 判断是否符合进行下一步交换操作的条件
/**
是否允许进行追踪
@param self 当前对象(类或者实例对象)
@param selector SEL
@param options 时机选项
@param error error对象指针
@return 是否允许
*/
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
BOOL isMetaClass = class_isMetaClass(object_getClass(self));
//如果是类则返回YES,否则返回NO
if (isMetaClass) {
//当前是类
Class klass = [self class];
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
Class currentClass = [self class];
//在继承链上查找是否有包含该函数的的类
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) {
// Find the topmost class for the log.
if (tracker.parentEntry) {
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) {
// Already modified and topmost!
return YES;
}
}
}while ((currentClass = class_getSuperclass(currentClass)));
// Add the selector as being modified.
currentClass = klass;
AspectTracker *parentTracker = nil;
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
[tracker.selectorNames addObject:selectorName];
// All superclasses get marked as having a subclass that is modified.
parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
}
return YES;
}
这个函数主要用于检查当前的参数是否符合进行方法交换的条件:
- 隔离SEL"黑名单":将retain,release,autorelease,forwardInvocation添加到数组中,并检查SEL是否包含在数组中,如果包含在数组中,则返回NO,同时输出错误信息.所以并不是所有的方法都可以使用Aspects进行hook操作;
- 传入的时机是否正确,判断SEL是否是dealloc,如果是dealloc,选择的调用时机必须是AspectPositionBefore,否则对象被销毁,一切就没有意义了;
- 判断类或者类对象是否响应传入的sel,也是就是判断我们需要hook的方法是都已经被实现;
- 如果传入的是操作对象是类,判断该类的isa指针是否指向了一个元类.如果指向元类,那么就需要对该类继承链上的所有的类进行sel标记,在这个继承链上该sel的hook操作不允许超过一次,也就是说在该类以及该类的继承链上,对于指定的sel不允许重复hook操作.
2.2 创建AspectsContainer容器类
@param self 需要hook的操作
@param selector 需要hook的selector
@return 容器类对象
*/
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
NSCParameterAssert(self);
//为当前selector添加aspects_前缀
SEL aliasSelector = aspect_aliasForSelector(selector);
//查看当前self下是否已经存在aliasSelector对应的AspectsContainer对象
AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
if (!aspectContainer) {
//如果不存在则进行创建,同时将aspectContainer保存到self对应的aliasSelector地址
aspectContainer = [AspectsContainer new];
objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
}
return aspectContainer;
}
- 首先,为selector对应的字符串添加对应的aspects_前缀得到aliasSelector;
- 动态获取当前self对象下是否有保存aliasSelector对应的AspectsContainer对象;
- 如果有则直接返回,否则创建一个AspectsContainer对象,并动态绑定到当前self对象下aliasSelector对应的空间.
2.3 创建AspectIdentifier对象保存hook内容
/**
创建AspectIdentifier对象
@param selector 方法编号
@param object 当前对象
@param options 切片时机
@param block 需要执行的block
@param error 是否产生错误
@return AspectIdentifier对象
*/
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
NSCParameterAssert(block);
NSCParameterAssert(selector);
//获取block的方法签名,使用OC中对于block的结构体定义定义了AspectBlockRef结构体,同时强制转化当前block为AspectBlockRef,使得变量对齐
NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
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的签名
@param block 自定义执行的block
@param error NSError
@return 方法签名
*/
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
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和原始的方法签名能否匹配(block的参数个数不大于原始方法的参数)
@param blockSignature block的方法签名
@param object 当前对象
@param selector 方法编号
@param error 是否有错误
@return 方法签名是否匹配
*/
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];
//如果block的参数个数多于selector参数个数,则返回NO
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
//要求block签名中的参数个数不多于selector的签名中的参数个数
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.(默认第一个参数是self或者block,第一个参数是SEL或者id<AspectInfo>,所以从第二个参数开始比较)
// 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:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature];
AspectError(AspectErrorIncompatibleBlockSignature, description);
return NO;
}
return YES;
}
在获取block签名的过程中,Aspects巧妙地使用了OC中关于block的定义,对当前的block进行了强制对齐转化,获取到了block最原始的结构体,同时对当前block的签名的中参数列表与selector的签名参数列表进行对比,要求block的参数列表个数要不大于selector否则无法传递参数.
2.4 把AspectIdentifier根据options添加到对应的数组中
/**
将AspectIdentifier添加到对应的几何中
@param aspect 需要添加的AspectIdentifier对象
@param options 切片时机选项
*/
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
NSParameterAssert(aspect);
NSUInteger position = options&AspectPositionFilter;
switch (position) {
case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break;
}
}
使用&运算,根据传入的切面时机,进行对应数组的存储.而且这个设计很有意思,因为AspectsContainer是与当前的需要hook的对象绑定的,所以你可以在同一个方法的执行前,执行后同时插入你想要的操作.
2.5 进行hook操作
2.5.1 aspect_hookClass
/**
置换对应的class
@param self 当前对象(类对象或者实例对象)
@param error 是否y出现错误
@return 被置换的的类对象
*/
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
/*在此处进行了置换单一对象的方法还是类方的判断
此处的statedClass和baseClass不完全一致:实例对象的class方法返回的是其对应的类,而类方法的class方法返回的是类本身;而object_getClass这个实现,如果参数是实例对象,返回值是指向指向生成该对象的类,而如果是类对象,返回值是指向类对象的元类(object_getClass实现就是返回参数对象的指针)
所以,以下判断的实现可以可以这样理解:
if (class_isMetaClass(baseClass)) {
//当前对象为类对象,即self是Class(class_class)类型,此时object_getClass返回的是指向该类对象的元类
return aspect_swizzleClassInPlace((Class)self);
} else {
//当前对象为实例对象,即self是一个id(class_object)类型,此时object_getClass返回的是指向生成该对象的类(不是元类)
//此时又有三种情形,1)如果类本身使用了KVO监测属性变化,则其父类是系统自动生成的一个中间类(NSKVONotifying_原始类名),这时就不再单独生成中间类;2)给类对象已经被hook过,就不再进行hook处理;3)当前实例对象需要进行hook处理
if(statedClass != baseClass) {
//上述三种情形中的 1)
return aspect_swizzleClassInPlace(baseClass);
} else if ([className hasSuffix:AspectsSubclassSuffix]) {
// 上述边三种情形中的 2)
return baseClass;
}
//剩下的就是要处理上述三种情形中的 3)
}
*/
// 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.
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);
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
- 区分当前处理的是实例对象还是类对象;
- 对于类对象:确定需要对方法forwardInvocation:进行置换的类,并将该类的forwardInvocation:实现指向自定义的函数AspectsForwardInvocationSelectorName(__aspects_forwardInvocation:),同时使用全局的NSMutableSet对象存储已经置换过的类,防止重复置换;
- 对于实例对象:分为三种情形处理
- 已经被Hook处理过的对象,直接返回其父类;
- 已经被被KVO监听监听的对象(父类已经是一个中间类),置换其父类的forwardInvocation:实现,并返回该父类;
- 未被Hook处理过且未被KVO监听属性的对象:生成一个带有AspectsSubclassSuffix前缀的中间类,并置换该类的forwardInvocation:实现,同时将中间类和中间类元类的class方法返回值都指向原始的父类,最后向系统注册中间类并将当前对象的父类指向生成的中间类.
会不会有人会有疑问,为什么要生成一个中间类?
这种手法我们在KVO的实现原理中见到过,这种实现手法对于KVO来讲,主要的好处有:
- 可以很好的隐藏相关处理的实现原理,让人不容易发现这个对象类被特殊处理过,例如开发者最初都被KVO如何实现的困惑过;
- 缩小了方法覆盖或者重定向的影响范围,我们知道其实实例对象的方法实现并不存储在实例对象中,而是存储在其父类对象中, 所以覆盖或者重定向方法实现会导致该类生成的所有对象都会被影响(尤其是如果我们改变了系统类的原始实现,其他地方生成的实例对象的行为也会改变而且很难被发现),而生成了中间类之后,就可以把这种影响限定在我们生成的中间类生成的对象中,避免对系统类的全局性影响;
- 可以使用类名作为一个单独的标记来标记该类被做过某种处理,下次再有该类的对象就不必再进行这种处理,避免了重复处理.
2.5.2 aspect_prepareClassAndHookSelector
/**
交换方法实现
@param self 当前对象(类对象或者实力对象)
@param selector 方法编号
@param error 是否出现错误(NSError指针地址)
*/
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);
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));
}
}
- 获取已经进行了forwardInvocation:的类;
- 判断需要进行hook的方法实现不是指向_objc_msgForward(32位操作系统中是_objc_msgForward_stret),如果不是则,使用新添加的aspects_selector指向原始的selector的实现;
- 将原始的selctor指向_objc_msgForward(32位操作系统中是_objc_msgForward_stret),这样只要调用了原始的selector方法,系统就会把实现通过消息转发机制转发到自定义的__ASPECTS_ARE_BEING_CALLED__进行处理.
2.5.3 __ASPECTS_ARE_BEING_CALLED__
-
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)]; }
- 将invocation的方法实现定向为原始的方法实现:根据invocation信息获取原始的方法实现的选择器selctor,因为方法已经被置换所以需要aspects_selector来获取到原始的方法实现;
- 在方法执行之前执行classContainer.beforeAspects和objectContainer.beforeAspects中的自定义操作;
- 根据options判断是执行原始操作还是替换原始操作;
- 在方法执行之后执行classContainer.afterAspects和objectContainer.afterAspects中的自定义操作;
- 异常处理