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 方法所在的类名。最后将当前实例self
的isa
指针指向这个子类,做用是当self
接受消息时,会先在这个子类方法列表查找。
此时若是self
收到的消息消息没法处理,会走forwardInvocation
对应的实现,而前面这个方法已经被 hook 指向咱们本身的实现,在咱们本身实现中,经过NSInvocation
能取出当前消息的接受对象,经过该对象从关联属性中取出 container 来执行。
如今的目标是若是经过调用被 hook 的方法,来触发forwardInvocation
的调用。
2)处理被 hook 的实现 在 <objc/message.h> 头文件中有一个函数_objc_msgForward
它的函数指针就是与 forwardInvocation
关联的imp
。
只要咱们将 hook 的方法与 _objc_msgForward
实现交换,就能触发forwardInvocation
的imp
了。
在调剂原selector
的imp
指向_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]
调用时,触发的仍是当前self
的selector
。也就致使了循环调用。
- (void)helloInstanceMethod {
[super helloInstanceMethod]; // 致使 [self helloInstanceMethod]
}
复制代码
Aspects 在类层面上进行方法的调剂时,直接“原地”调剂forwardInvocation
指向本身的实现,并将要 hook 的方法指向消息转发的实现。同时它必须保证同一条继承链上,不能 hook 同一个方法。
然而,对于实例对象的方法却不须要,由于实例方法的 hook 会动态的建立子类,并修改消息转发的实现,保证原类的其余实例方法不会受影响。