Aspects 实现原理

Aspects 是 Objective-C 比较知名的 AOP 框架,实现方法调剂(method swizzling)。经过使用 Aspects 提供的接口,比直接使用 runtime 提供的接口,更加方便灵活。ios

Aspects 如今不建议在生产环境使用,但它的实现原理,仍是很是值得学习和借鉴的。git

Aspects 支持实例和类的方法的调剂,虽然内部调用的是同一个方法,在实现上有较大的差异。github

实例的方法交换

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error {
    return aspect_add(self, selector, options, block, error);
}
复制代码

- aspect_hookSelector内部调用的aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error)由 C 函数实现。该函数有五个参数:数组

  • self:当前调剂方法的对象;
  • selector:被调剂的方法名;
  • options:调剂的方式类型为 AspectOptions;
  • block:调剂selector方法是须要混入执行的 block;
  • error: 调剂过程发生的错误信息;

AspectOptions 有四种类型,用于决定 block 和原方法执行的顺序,分别有在原方法以前、以后、或者直接替换。安全

咱们逐一看看 aspect_add 函数的内部实现:框架

__block AspectIdentifier *identifier = nil;
// 1
aspect_performLocked(^{
	// 2
    if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
        // 3
        AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
        // 4
        identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
        if (identifier) {
            // 5
            [aspectContainer addAspect:identifier withOptions:options];
            // 6
            aspect_prepareClassAndHookSelector(self, selector, error);
        }
    }
});

复制代码

一、 为方法调剂加锁,保证线程安全,使用的是自旋锁,然而 OSSpinLockLock 已再也不安全ide

二、 判断当前 selector 是否支持调剂。若是被调剂方法是 retain, release, autorelease, forwardInvocation:则返回错误。forwardInvocation方法不支持调剂是由于 Aspects 的实现基于这个方法,后面会讲到。至于其余几个内存管理的方法,笔者不是很肯定不能调剂的缘由,多是在这些方法中对象处于不稳定状态, 访问当前对象存在安全隐患。函数

接着是保证调剂dealloc方法只能是AspectPositionBefore类型,dealloc 负责释放资源,不能被替换,执行后对象释放不能再执行 block 操做。学习

最后判断当前对象可否响应被调剂的方法。ui

三、 aspectContainer以关联对象的形式做为self的属性,属性的名称为 selector加前缀aspects_

@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
复制代码

AspectsContainer类包含三个数组属性,分别用于保存在 block 三种执行的状况。

四、 AspectIdentifier以对象的形式封装调剂信息,其中包含方法名,执行的对象,执行时机,以及执行的 block。

五、 将 aspect identifier 存储到当前对象关联的 container 中。在方法被调用的时候,会遍历这个 container 中全部的 identifier,根据它包含的信息来执行。

六、 这是最关键的一步,也是 Aspects 实现的核心所在。主要分为两部分,

1)处理被 hook 方法的对象所属的类

Class aspect_hookClass(NSObject *self, NSError **error)
复制代码

若是对象所属的类已经被 Aspects 处理过就直接返回,不然该函数为当前实例对象所属的类,动态的建立一个子类,并 hook 该子类的forwardInvocation方法,指向本身的__ASPECTS_ARE_BEING_CALLED__函数实现。

重置该子类对象和元类的 class 方法,使其返回(父类)被 hook 方法所在的类名。最后将当前实例selfisa指针指向这个子类,做用是当self接受消息时,会先在这个子类方法列表查找。

此时若是self收到的消息消息没法处理,会走forwardInvocation对应的实现,而前面这个方法已经被 hook 指向咱们本身的实现,在咱们本身实现中,经过NSInvocation能取出当前消息的接受对象,经过该对象从关联属性中取出 container 来执行。

如今的目标是若是经过调用被 hook 的方法,来触发forwardInvocation的调用。

2)处理被 hook 的实现 在 <objc/message.h> 头文件中有一个函数_objc_msgForward它的函数指针就是与 forwardInvocation关联的imp

只要咱们将 hook 的方法与 _objc_msgForward实现交换,就能触发forwardInvocationimp了。

在调剂原selectorimp指向_objc_msgForward函数以前,须要先判断该 selector 是否已经被调剂过,若是已是_objc_msgForward的实现就不做处理。

若是不是,咱们要对selector的实现作交换了,假如咱们直接将其与 _objc_msgForward交换,若是对象的另外一个selector也作 hook,就会把前一个selector的实现给覆盖了。因此这里动态的添加一个 aliasSelector方法用于保存原selector的实现,再用_objc_msgForward函数替换原 selector 的实现。

当被调剂的方法执行时,会执行动态建立的子类的forwardInvocation方法,而在1)中咱们讲到该方法的实现已经被 hook,指向了 __ASPECTS_ARE_BEING_CALLED__函数。

__ASPECTS_ARE_BEING_CALLED__函数的实现其实并不复杂,它从 forwardInvocation的参数NSInvacation 中取出触发消息转发的 selector,将它替换为aspects_前缀的别名selector,由于这个别名 selector指向前面保存的原selector的实现。

接着从self中取出使用以别名selector做为属性的关联对象 AspectsContainer,container 中包含selector方法被调剂的信息集合。先执行 before 的 block,接着执行封装在invocation中的原实现,最后执行 after 类型的 block。若是有 instead 类型的 block 则原实现不会被执行。

其中 block 并非直接执行 aspect 中保存的那个,而是对其参数进行了处理,在第一个参数中插入 AspectInfo 类型的实例。

类的方法交换

类的方法交换流程与实例的方法交换大体相同,主要区别在于对同一继承链上的类 hook 相同的方法进行了限制。

实例和类调用的都是aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error)函数,因此 self参数多是实例,也多是类,源码中使用 object_getClass,若是是实例对象会返回类,若是是类会返回元类,再经过class_isMetaClass判断self是不是为类。

aspect_isSelectorAllowedAndTrack函数中,对类的状况作了一大堆处理,其实就作一件事情:判断当前self所在的继承链是否有同名的 selector已经被 hook 过了。

若是没有,就记录当前self的继承关系,直到基类NSObject

之因此不能 hook 具备继承链关系同名方法,是由于若是子类方法调用super会致使死循环。

关键缘由在于super关键字的实现,它是编译器的一个助记符,在方法调用时,会在父类的方法列表中查找,但无论在哪一层级找到,消息的接受者仍然是当前类/实例。也就是说在执行super方法时,若是父类的该方法也被调用,就会走__ASPECTS_ARE_BEING_CALLED__函数,该函数中的 NSInvocation 参数中的 target依然是self,这就致使[invocation invoke]调用时,触发的仍是当前selfselector。也就致使了循环调用。

- (void)helloInstanceMethod {
    [super helloInstanceMethod]; // 致使 [self helloInstanceMethod]
}
复制代码

对比总结

Aspects 在类层面上进行方法的调剂时,直接“原地”调剂forwardInvocation指向本身的实现,并将要 hook 的方法指向消息转发的实现。同时它必须保证同一条继承链上,不能 hook 同一个方法。

然而,对于实例对象的方法却不须要,由于实例方法的 hook 会动态的建立子类,并修改消息转发的实现,保证原类的其余实例方法不会受影响。