一、我们直接从接口的入口开始分析:
aspect_performLocked:线程安全处理
aspect_isSelectorAllowedAndTrack:进行参数的校验
AspectsContainer*aspectContainer = aspect_getContainerForObject(self,
selector):得到已有或创建新的AspectsContainer,现在你只需知道有这个对象就行了,后面用到再分析
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:blockerror:error]:这个对象才是主要的,跟很多操作都有关系,上面的AspectsContainer主要也是为了得到它。也是一样,后面用到再分析。
二、aspect_prepareClassAndHookSelector:开始了神奇的hook操作,这函数不得了。我们进去看看作者怎么hook的。
幸好代码也不多,遇到代码较多的函数,我们先不进去细看,可以先靠猜。
Classklass = aspect_hookClass(self, error):返回一个类,我们可以大概猜到,这个类肯定跟原类有关,那么就先认为它是原类的子类吧!
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod):
获得selector对应的方法实现IMP
if (!aspect_isMsgForwardIMP(targetMethodIMP)):这函数简单,进去看看,就是判断impl == _objc_msgForward是否相等,_objc_msgForward一个没实现的函数。我就叫它空函数吧!
下面的,直接看注释吧,拷贝代码对齐好累,不一一分析了。这段代码总的意思是:
1、通过一个新的selector(aspect_aliasForSelector返回一个重新命名的SEL)关联原来的函数实现IMP。即是保存了原来了函数的IMP,为了之后可以调用,因为下面我们要修改它的selector,如果不重新指向,就再也找不到了。
2、把原来的selector关联到上面提到的空函数,原来的函数重新指向了,但指向的是空。
就这样,那么问题来了,原来的selector执行了空函数,就是说,调用后,就会因为没有实现,便会做以下的求救处理:
resolveInstanceMethod(NO)—>forwardingTargetForSelector(nil或self)—-> (methodSignatureForSelector返回NSMethodSignature)forwardInvocation
有三个补救方法,其中源码用的是forwardInvocation来补救。就是说hooks的就是forwardInvocation函数。
forwardInvocation做了什么呢?我们可以根据上下文推导下:
如果配置的是before,就是在调用原来方法前调用我们的block。
1、那么就是先调用我们的block
2、再根据之前保存的原函数aliasSelector,调用原来的函数。
你带着这个想法去看aspect_hookClass的实现,会简单点。
三、aspect_hookClass函数分析:
上面的方法构建一个子类。当然,如果传入的已经是类了,那么就不需要创建子类了。然后就hook forwardInvocation方法。
这里可以看到,我们之前的猜测是对的,确实是创建了原类的子类。
然后是:
aspect_swizzleForwardInvocation(subclass);
真正的hook操作在这里。hook forwardInvocation方法于__ASPECTS_ARE_BEING_CALLED__
把forwardInvocation指向了新的函数__ASPECTS_ARE_BEING_CALLED__
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"):这个是,如果被hook的方法原本已是通过forwardInvocation来处理,那么就保存原本这个forwardInvocation的实现,以便后面调用。这个不是重点。
想不到还要再进一层:__ASPECTS_ARE_BEING_CALLED__
还是以before做分析吧:
这里做到上面提到了两个推测:
1、先执行了指定的block :
aspect_invoke(objectContainer.beforeAspects, info);
2、再调用原来方法:
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocationinvoke];
break;
}
AspectsContainer *objectContainer = objc_getAssociatedObject(self,aliasSelector);
拿到一开始创建的那个AspectsContainer,你可以看回最前面那里。它beforeAspects存的是之前的AspectIdentifier。
四、然后分析下是如何执行block的:
- (BOOL)invokeWithInfo:(id)info
执行block方法:[blockInvocationinvokeWithTarget:self.block];
它的第一个参数为:传入来的AspectInfo
[blockInvocationsetArgument:&info atIndex:1];
所以它的接口说只用一个参数(id)info就可以了,因为它的arguments属性存了正在的所有参数,转换为数组返回:
五、分析下前面的AspectIdentifier,既然那么重要
1、[AspectIdentifier identifierWithSelector:selector object:self options:optionsblock:block error:error];最开始的初始化:
NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error);
把block转为通过签名调用,所以才有了后面:
//block的NSInvocation
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
[blockInvocationinvokeWithTarget:self.block];
整体代码架构已分析完成,具体的东西你再细看。
六、整份代码还是有很多干货的:
1、函数还可以这样调用:
先获取函数签名:NSMethodSignature
再通过签名转化为:NSInvocation
最后通过NSInvocation设置参数,target,然后就invoke调用
2、block也可以转换为NSInvocation来调用,源码里转换过程还是有点神奇的,你去看看《OC高级这本书》block,获悉能理解转换过程。Block其实就是一个对象。
3、对类,元类等概念的理解