Aspects 源码分析

Aspects是一个用于实现面向切片编程(AOP, Aspect-oriented programming )思想的第三方库。面向切片编程,指的是在运行时,动态地将代码切入到类的指定方法、指定位置上,从而得到改变方法的实现的目的。由于 Objective-C 本身是一门非常动态的语言,因此 AOP 在 Objective-C 这门语言中很容易利用 Runtime 进行实现。这个库里的代码不多,只有Aspects.hAspects.m两个文件。头文件里定义了一个 NSObject 的分类,给所有 NSObject 的子类添加了如下两个方法:

第一个是类方法,而另一个是实例方法,其实内部都调用了同样的方法,只不过传给那个方法的参数稍微有些区别。
下面我们就结合着Aspects给出的一个 Demo 来分析它的内部做了什么事情。Demo 的核心代码是下面这么一段,也就是当我们点击 button 后,会出现照片选取的 UIImagePickerController,而在UIImagePickerControllerviewWillDisappear方法被调用时,程序此时就会调用我们设置的钩子函数,显示一个 UIAlertView。

testController Hook 了两个viewWillDisappear:dealloc这两个方法。options 指定了 Hook 的时机,可以在指定的时机执行 block 内的方法:

所以 testController会在viewWillDisappear调用后以及dealloc调用前执行block 中的方法。
下面我们看下 hook 这个方法究竟做了什么。进入

其实它调用了 aspect_add 这个静态函数:

第1步,aspect_performLocked这个方法在执行作为参数的 block 前会进行加锁操作,而在 block 执行完后进行解锁:

第2步中调用了aspect_isSelectorAllowedAndTrack(self, selector, options, error)这个方法:

第3步,我们以self 和 selector 为参数,调用aspect_getContainerForObject函数,返回一个AspectsContainer

AspectsContainer 和 SEL 是通过关联对象技术关联在一起的,因此这个方法里就是通过 SEL 取出对应的 AspectsContainer 实例。
第4步是创建出一个AspectsIdentifier实例:

第5步是将上一步中创建的AspectIdentifier实例添加到第3步创建的AspectsContainer中:

第6步是整个方法 Hook 的核心:

这个方法也比较复杂,因此我们也是将它分成四个小步骤来分析。
第(1)步,调用aspect_hookClass

第(2)步,调用了aspect_isMsgForwardIMP来判断我们要替换的 SEL 的 IMP 是不是_objc_msgForward,如果不是才会进行第(3)、(4)步的操作

第(3)步,创建出一个新的 SEL,这个新的 SEL 是由被替换的 SEL 的字符串加上一个特定前缀字符串生成的,然后将它添加到上面新创建的类中:

第(4)步,将selector 对应的实现 IMP 替换为_objc_msgForward

所以我们可以总结下第6步这个方法做了什么事情:
(1) 创建了一个新的类,然后将这个新类的forwardInvocation: 实现替换为了__ASPECTS_ARE_BEING_CALLED__,并且将这个新类的类对象和元类的 class 修改为原来的类,最后将 self 所属的类修改为新类。
(2) 将 selector 对应的 IMP 实现替换为forwardInvocation:
以上介绍的1~6步,完成了 Hook 的动作。那么在被 Hook 的对象执行被替换的方法时,运行时就会转而执行__ASPECTS_ARE_BEING_CALLED__这个方法。接下来我们看下这个方法里有什么奥秘。

所以这个方法的作用就是根据 option 的设置,在合适的时机调用原来的方法实现以及我们的钩子函数。
以上就是 Aspects这个库进行方法 Hook 的基本原理了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据