以技术之名周报03#AOP| 2020-04-12

Part01-什么是AOP?

面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。

Part02-AOP一个使用场景:埋点

互联网进入到下半场,用户也由增量进入到存量时代。通过统计用户操作行为了解用户的偏好,结合大数据分析,可以方便公司更加合理的作出决策更好的吸引客户、留住客户。对任何一个主流的APP来讲,都会存在大量的埋点来统计用户的行为。埋点功能不属于业务逻辑,埋点代码和业务代码混合在一块会严重污染业务代码,且埋点的代码会分散到整个工程。面向切面编程是解决这种问题一个很好的方案,其原理就是在不更改正常业务处理流程的前提下,动态生成一个代理类,从而实现对目标对象嵌入附加的操作。

Part03-iOS实现AOP的原理

利用OC的动态性,通过wizzling method 改变目标函数的 selector 所指向的实现,然后在新的实现中实现附加的操作,完成之后再回到原来的处理逻辑。OC是一门动态语言,在OC中执行一个方法的时候实际上是在发送一条消息给接受对象[receiver message]。完整的过程是根据message生成一个选择子selector,然后根据selector找到指向函数的的具体的实现的指针IMP,然后找到真正的函数执行逻辑。OC的这种处理流程为我们动态修改函数的实现提供了可能,在运行时,只要我们改变selector和IMP的对应关系,我们就可以在执行[receiver message]时进入到新的函数实现。

Part04- iOS实现AOP的一个成熟的开源框架:Aspects

Aspects的实现思路

Aspects是利用 oc 自己的消息转发机制进行转发,在解释这个过程之前,我们先看一下消息是怎么找到对应的IMP的

从上面我们可以发现,在发消息的时候,如果 selector 有对应的 IMP ,则直接执行,如果没有,oc 给我们提供了几个可供补救的机会,依次有 resolveInstanceMethodforwardingTargetForSelectorforwardInvocation。Aspects 之所以选择在 forwardInvocation 这里处理是因为,这几个阶段特性都不太一样:resolvedInstanceMethod 适合给类/对象动态添加一个相应的实现,forwardingTargetForSelector 适合将消息转发给其他对象处理,相对而言,forwardInvocation 是里面最灵活,最能符合需求的。因此 Aspects 的方案就是,对于待 hookselector,将其指向 objc_msgForward / _objc_msgForward_stret ,同时生成一个新的 aliasSelector 指向原来的 IMP,并且 hookforwardInvocation 函数,使他指向自己的实现。按照上面的思路,当被 hook selector 被执行的时候,首先根据 selector 找到了objc_msgForward / _objc_msgForward_stret,而这个会触发消息转发,从而进入 forwardInvocation。同时由于 forwardInvocation 的指向也被修改了,因此会转入新的 forwardInvocation 函数,在里面执行需要嵌入的附加代码,完成之后,再转回原来的 IMP

源码分析

数据结构

从头文件中可以看到使用aspects有两种使用方式:(1)类方法 ;(2)实例方法

1
2
3
4
5
6
7
8
9
10
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;

两者的主要原理基本差不多,这里不做一一介绍,只是以实例方法为例进行说明。在介绍之前,先介绍里面几个重要的数据结构:

AspectOptions

block 执行的时机,也就是额外操作的执行时机,它可以在原始函数执行之前,也可以是执行之后,甚至可以完全替换掉原来的逻辑。

1
2
3
4
5
6
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// Called after the original implementation (default)
AspectPositionInstead = 1, /// Will replace the original implementation.
AspectPositionBefore = 2, /// Called before the original implementation.
AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};
AspectsContainer

一个对象或者类的所有的 Aspects 整体情况:一个对象和类可能有多个Aspect,每个Aspect的执行的时机都是不一样的,AspectsContainer存储了这个类或对象全部的Aspect

1
2
3
4
5
6
7
8
9
// Tracks all aspects for an object/class.
@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
AspectIdentifier

一个 Aspect 的具体内容,这里主要包含了单个的 aspect 的具体信息,包括执行时机,要执行 block 所需要用到的具体信息:包括方法签名、参数等等

1
2
3
4
5
6
7
8
9
10

@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
AspectInfo

一个 Aspect 执行环境,主要是 NSInvocation 信息

1
2
3
4
5
6
@interface AspectInfo : NSObject <AspctInfo>
- (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

代码流程

有了上面的了解,我们就能更好的分析整个 apsects 的执行流程。添加一个 aspect 的关键流程如下图所示:

1
2
3
4
5
6
7
8
9
10
11
tatic id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
...
__block AspectIdentifier *identifier = nil;
aspect_performLocked(^{
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {//1判断能否hook
...//2 记录数据结构
aspect_prepareClassAndHookSelector(self, selector, error);//3 swizzling
}
});
return identifier;
}
判断能否被 hook

对于对象实例而言,这里主要是根据黑名单,比如 retain forwardInvocation 等这些方法在外部是不能被 hook ,(对于类对象还要确保同一个类继承关系层级中,只能被 hook 一次,因此这里需要判断子类,父类有没有被 hook,之所以做这样的实现,主要是为了避免出现死循环的出现,这里有相关的讨论)。如果能够 hook,则继续下面的步骤。

swizzling method

这是真正的核心逻辑,swizzling method 主要有两部分,一个是对对象的 forwardInvocation 进行 swizzling,另一个是对传入的 selector 进行 swizzling.

1
2
3
4
5
6
7
8
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
Class klass = aspect_hookClass(self, error); //1 swizzling forwardInvocation
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {//2 swizzling method
...//
}
}
swizzling forwardInvocation:

aspect_hookClass 函数主要 swizzling 类/对象的 forwardInvocation 函数,aspects 的真正的处理逻辑都是在 forwradInvocation 函数里面进行的。对于对象实例而言,源代码中并没有直接 swizzling 对象的 forwardInvocation 方法,而是动态生成一个当前对象的子类,并将当前对象与子类关联,然后替换子类的 forwardInvocation 方法(这里具体方法就是调用了 object_setClass(self, subclass) ,将当前对象 isa 指针指向了 subclass ,同时修改了subclass以及其 subclass metaclassclass 方法,使他返回当前对象的 class。它的原理有点类似 kvo 的实现,它想要实现的效果就是,将当前对象变成一个 subclass 的实例,同时对于外部使用者而言,又能把它继续当成原对象在使用,而且所有的 swizzling 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类,也就是,当你在remove aspects的时候,如果发现当前对象的 aspect 都被移除了,那么,你可以将 isa 指针重新指回对象本身的类,从而消除了该对象的 swizzling ,同时也不会影响到其他该类的不同对象)。对于每一个对象而言,这样的动态对象只会生成一次,这里aspect_swizzlingForwardInvocation将使得 forwardInvocation 方法指向 aspects 自己的实现逻辑 ,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static Class aspect_hookClass(NSObject *self, NSError **error) {
...
//生成动态子类,并swizzling forwardInvocation方法
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
aspect_swizzleForwardInvocation(subclass);//swizzling forwardinvation方法
objc_registerClassPair(subclass);
...
object_setClass(self, subclass);//将当前self设置为子类,这里其实只是更改了self的isa指针而已
return subclass;
}
...
static void aspect_swizzleForwardInvocation(Class klass) {
...
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@")
}
...
}

由于子类本身并没有实现 forwardInvocation ,隐藏返回的 originalImplementation 将为空值,所以也不会生成 NSSelectorFromString(AspectsForwardInvocationSelectorName)

swizzling selector

forwradInvocationhook之后,接下来,将对传入的selector进行 hook ,这里的做法是,将 selector 指向了转发 IMP ,同时生成一个 aliasSelector ,指向了原来的IMP,同时为防止重复 hook ,做了一个判断,如果发现 selector 已经指向了转发 IMP ,那就就不需要进行交换了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
tatic void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
...
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
...
SEL aliasSelector = aspect_aliasForSelector(selector);//generator aliasSelector
if (![klass instancesRespondToSelector:aliasSelector]) {
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
}
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);// point to _objc_msgForward
...
}
}
handle ForwardInvocation

基于上面的代码分析知道,转发最终的逻辑代码最终转入 __ASPECTS_ARE_BEING_CALLED__ 函数的处理中。这里,需要处理的部分包括额外处理代码(如打点代码)以及最终重新转会原来的selector所指向的函数,其实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
...
// Before hooks. 原来逻辑之前执行
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {//是否需要替换掉原来的路基
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
} else {
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];//根据aliasSelector找到原来的逻辑并执行
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks. 原来逻辑之后执行
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {//找不到aliasSelector的IMP实现,没有找到原来的逻辑,进行消息转发
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
} else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
...
}

依次处理 before/instead/after hook 以及真正函数实现。如果没有找到原始的函数实现,还需要进行转发操作。

参考

面向切面编程之 Aspects 源码解析及应用