AOP 面向切面

文章目录
  1. 1. 概念
    1. 1.1. 作用&意图
    2. 1.2. 实现方式
  2. 2. iOS 中的 AOP
    1. 2.1. 实际应用
      1. 2.1.1. 事务拦截,安全可变容器
  3. 3. Aspects 一个基于Objective-c的AOP开发框架
  4. 4. Aspects 源码分析

概念

概念:把一个个的横切关注点(某种业务的实现代码)放到某个模块中去,称之为切面。每个切面影响业务的一种功能,切面的目的就是为了功能增强,将需要增强的方法做成切面,实现对业务的增强,就是面向切面编程。

AOP实际是GoF设计模式的延续,设计模式追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。

作用&意图

主要用于:日志记录,性能统计,安全控制,事务处理,异常处理等等。

主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改 变这些行为的时候不影响业务逻辑的代码。

目的:将与业务本身无关,却被业务模块所共同调用的功能代码封装成切面,以减少系统的重复代码,降低耦合,提高可扩展性。
优势:把多个方法前/后的共同代码抽离出来,使用动态代理机制来控制,先执行抽离出来的代码,再执行每一个真实方法.
no aop
aop

实现方式

通过预编译方式,运行期动态代理,实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
注意:AOP不是一种技术,实际上是编程思想。凡是符合AOP思想的技术,都可以看成是AOP的实现

iOS 中的 AOP

利用 Runtime 特性 Method Swizzling 。
AOP也可以用来在 debug 模式做一些开发中的应用

实际应用

事务拦截,安全可变容器

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
// 如果保证线程安全在每个方法中加锁
@implementation NSMutableArray (safe)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
id obj = [[self alloc] init];
[obj swizzleMethod:@selector(addObject:) withMethod:@selector(safeAddObject:)];
[obj swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(safeObjectAtIndex:)];
[obj swizzleMethod:@selector(insertObject:atIndex:) withMethod:@selector(safeInsertObject:atIndex:)];
[obj swizzleMethod:@selector(removeObjectAtIndex:) withMethod:@selector(safeRemoveObjectAtIndex:)];
[obj swizzleMethod:@selector(replaceObjectAtIndex:withObject:) withMethod:@selector(safeReplaceObjectAtIndex:withObject:)];
});
}

- (void)safeAddObject:(id)anObject {
if (anObject) {
[self safeAddObject:anObject];
}else{
NSLog(@"obj is nil");
}
}

- (id)safeObjectAtIndex:(NSInteger)index {
if(index<[self count]){
return [self safeObjectAtIndex:index];
}else{
NSLog(@"index is beyond bounds ");
}
return nil;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector{
Class class = [self class];

Method originalMethod = class_getInstanceMethod(class, origSelector);
Method swizzledMethod = class_getInstanceMethod(class, newSelector);

BOOL didAddMethod = class_addMethod(class,
origSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
method_exchangeImplementations(originalMethod, swizzledMethod);
} else {
class_replaceMethod(class, newSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
}

safeAddObject 代码看起来很奇怪,像递归不是么。当然不会是递归,因为在 runtime 的时候,函数实现已经被交换了。调用 objectAtIndex: 会调用你实现的 safeObjectAtIndex:,而在 NSMutableArray: 里调用 safeObjectAtIndex: 实际上调用的是原来的 objectAtIndex: 。

Aspects 一个基于Objective-c的AOP开发框架

  1. 怎么 hook block
  2. 如何把hook 简化成了简单接口?

主要接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 主要接口
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;

//业务埋点、日志打印分离
@implementation UIViewController (Logging)
+ (void)load{
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<aspectinfo> aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
} error:NULL];
}

Aspects 源码分析

缕清框架在进行细节源码分析

frame

使用知识点

  1. block --> signature
  2. KVO–> subClass
  3. MessageForwarding & swizzle

aspects_hookClass

  1. 动态生成一个子类——> 用于hook,这样对原有类 0 影响(KVO实现)
  2. aspect_swizzleClassInPlace
  3. aspect_swizzleForwardInvocation swizzle forwardInvocation
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// This is a macro so we get a cleaner stack trace.
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}

// This is the swizzled forwardInvocation: method.
// 这个方法替换/添加了,forwardInvocation 方法。所有方法最终都会调用这个方法
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
SEL originalSelector = invocation.selector;
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
invocation.selector = aliasSelector;
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;

// 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];
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) {
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];
}
}

// Remove any hooks that are queued for deregistration.
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}