最近找工做受挫,无头苍蝇通常,挣扎焦虑的状态实在是难受。决心改变这样的状态而且提升本身,那就从最简单的静下心来细扣优秀源码开始吧。ios
Aspects是一个轻量级的面向切面编程(AOP)的库。它主要提供了三个切入点:before(在原始的方法前执行)/instead(替换原始的方法执行)/after(在原始的方法后执行,默认),经过Runtime消息转发实现Hook。它支持Hook某个实例对象的方法。而且它的内部考虑到了大量的可能触发的问题并进行相应的处理来确保安全。相比于单纯交换两个IMP的Method Swizzling
优点仍是很明显的。编程
阅读源码前仍是要本身先去试用一下,通常在这个试用的过程中你或多或少的都是会有一些疑问的。带着这些疑问去阅读源码的时候你就可能会有一些针对性。从某个具体的细节问题切入进去比单纯泛泛的看源码的效果来的好。我这里抛砖引玉的提两个问题。安全
Aspects提供了两个方法,一个对象方法一个是类方法。bash
+ (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;
复制代码
看了一些文章的介绍都是说-
号的方法是Hook实例方法的,+
方法是Hook类方法的,可是这里有个疑问Aspects其实提供的是三种功能的Hook框架
三种功能对应的是两个方法,咋作区分呢。(其实了解对象内存布局的,应该立刻能反应过来要怎么操做)less
通常常见的源码分析的文章喜欢从某个接口切入,从上往下的看整个代码的执行过程,而后最后在得出一个结论或者框架图。可是我感受这样的方式对应读者来讲相对是不太友好的,有的时候读者还没对整个框架大体了解,这时候一大推的源码贴上来读者是一脸蒙蔽的。我这里先对整个框架的结构大体作一层介绍,省略了一些细节具体的过程。ide
forwardInvocation
的IMP已经指向了咱们新写的方法,因此最后的before/instead/after的逻辑都是在咱们新写的方法里了。
看我上面大体的流程图,而后结合本身的runtime的知识再回过头来看看上面的两个问题。(若是对runtime不太熟悉的推荐霜神的博客连接1 连接2 连接3 )函数
从上面的流程图中咱们看到,当Hook实例对象的时候实际上是建立了一个新的class,而后让当前实例对象的isa指向了这个新类。因此和未被Hook的实例对象的isa指向的实际上是两个类对象了。而且原先类对象并未作任何处理。源码分析
这个问题其实就更简单了,咱们都知道其实类方法是存在metaClass里的,因此想要Hook类对象,拿到metaClass就能够了。布局
[xxx class]
和object_getClass(xxx)
的差异须要注意一下
[xxx class]
当xxx
是实例对象的时候返回的是类对象,其它状况返回的是本身。object_getClass(xxx)
返回的是当前对象isa指向的对象OK,如今咱们大体已经对整个流程有了一点点了解了。接下来咱们就须要去深挖一些细节了。Aspects内部的注释仍是很是全的~
@protocol AspectToken <NSObject>
//注销一个Hook
- (BOOL)remove;
@end
复制代码
这是个协议,内部就一个remove方法。遵循这个协议须要实现remove方法去注销Hook。
/// Hook的Block的第一个参数,遵循这个协议
@protocol AspectInfo <NSObject>
/// 当前Hook的对象
- (id)instance;
/// Hook的原方法的Invocation
- (NSInvocation *)originalInvocation;
/// 全部的方法参数
- (NSArray *)arguments;
@end
复制代码
咱们添加的Hook的block的第一个参数,遵循这个协议
AspectInfo协议遵循上面AspectInfo协议。三个属性和协议上一一对应。
@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end
复制代码
这里着重看一下arguments也就是方法的参数获取。内部的获取逻辑是originalInvocation
调用了NSInvocation
分类的方法- (NSArray *)aspects_arguments
。
- (NSArray *)aspects_arguments {
NSMutableArray *argumentsArray = [NSMutableArray array];
for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
[argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null];
}
return [argumentsArray copy];
}
复制代码
你能够看到上面方法的逻辑很简单就是遍历methodSignature
的Arguments
,可是你确定也注意到了idx
是从2开始的。经过查看官方文档能够看到这么一句话。
A method signature consists of one or more characters for the method return type, followed by the string encodings of the implicit arguments self and _cmd, followed by zero or more explicit arguments
也就是说一个方法的签名是由返回值 + self + _cmd + 方法参数的encodings值组成
可是这里方法参数是从3开始的,咱们接下去看到获取到具体类型是经过- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx
方法。OK,咱们又在这个方法的文档里看到了这么一句话。
Indexes begin with 0. The implicit arguments self (of type id) and _cmd (of type SEL) are at indexes 0 and 1; explicit arguments begin at index 2.
咱们发现0对应的是self
并非返回值。因此很显然了获取参数要从2开始啦。
总结:AspectInfo主要是对NSInvocation的保存和封装。
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end
复制代码
从这个类的初始化方法里咱们能看出来,这个类主要是保存了Hook的一些信息,hook的执行时间方法参数等等的信息。这个类里须要关注的地方是怎么解析出传入Block的blockSignature
。主要经过下面的方法。
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];
}
复制代码
在上面的方法里咱们注意到了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;
复制代码
在回去头去理一下方法逻辑。拿到descriptor
的指针,对照结构体中signature
的位置偏移2 * sizeof(unsigned long int)
的位置,而后在判断是否包含Copy和Dispose函数(copy函数把Block从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。参考霜神的博客),包含的话再偏移2 * sizeof(void *)
位置,最后拿到signature
的位置。拿到blockSignature
后续还对其进行了一下校验。
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, thats 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;
}
复制代码
仔细走一遍上面的逻辑,主要是判断block有参数的状况下必须是 id< AspectInfo > + 原始方法的参数顺序
(参数能够不全,可是顺序必须是对的)
总结:
AspectIdentifier
是一个Hook的具体内容。里面会包含了单个的 Hook 的具体信息,包括执行时机,要执行 block所须要用到的具体信息:包括方法签名、参数等等。
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end
复制代码
总结:这个类仍是很简单的,就是存了一些Hook的
AspectIdentifier
。
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, readonly) NSString *trackedClassName;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end
复制代码
总结:这个类主要的做用是追踪每一个类Hook的
selector
状况。确保一条继承链上只有一个类Hook了这个方法。
经过头文件看见公开的API就两个
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add((id)self, selector, options, block, error);
}
/// @return A token which allows to later deregister the aspect.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add(self, selector, options, block, error);
}
复制代码
这两个API最后走的都是同一个方法。
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(^{// 锁保证线程安全
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {// 判断是否能够Hook
// 根据方法拿到AspectIdentifier容器
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
// 根据selector self options block 生成AspectIdentifier
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
// 把生成AspectIdentifier添加进容器
[aspectContainer addAspect:identifier withOptions:options];
// 处理类
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
复制代码
这里咱们只关注aspect_prepareClassAndHookSelector
这个方法。
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));
}
}
复制代码
咱们先忽略Class klass = aspect_hookClass(self, error);
来看一下下面的逻辑。相对仍是比较直观的,主要的操做就是将当前的selector指向_objc_msgForward
,那么当调用方法的时候会跳过前面的经过isa查找IMP的流程,直接就走消息转发了。最后咱们来看一下aspect_hookClass
这个核心方法。
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// 类对象
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);
}
// 实例对象
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;
}
复制代码
从方法的逻辑中咱们看到
调用的是aspect_swizzleClassInPlace
方法,这个方法主要的操做是class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
也就是将消息转发的方法替换成咱们本身的方法__ASPECTS_ARE_BEING_CALLED__
。
若是你熟悉KVO的底层实现的话,你必定知道isa混写,也就是咱们偷偷摸摸生成了一个新的类对象,而后咱们对这个类对象作了和1相同的操做,而且咱们Hook了class方法让外面看起来咱们好像并无作这个操做。最后咱们将实例对象的isa指针指向了这个对象。你感兴趣的话,能够照着这个逻辑尝试下本身实现KVO。
最后理一下,当咱们调用方法的时候就会直接走消息转发,消息转发的forwardInvocation
已经替换成了咱们的__ASPECTS_ARE_BEING_CALLED__
。因此最后的具体执行逻辑就走这个方法里面了。
我这里只是对Aspects作了一个很浅的介绍,但愿能对你们有所帮助,也请你们多多指教~