iOS - 面向切面编程(AOP)

3,494 阅读2分钟

AOP: Aspect Oriented Programming 面向切面编程

一、什么是AOP

  • 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
  • AOP是OOP的延续,函数式编程的一种衍生范型

二、AOP的优势

  • 对业务逻辑的各个部分进行隔离
  • 降低业务逻辑各部分之间的耦合度
  • 提高程序的可重用性
  • 提高了开发的效率

三、AOP在iOS的应用 - runtime

利用iOS的runtime,我们可以做很多移花接木的事情,让人首先联想到的就是Method Swizzle,对于Method Swizzle,这里不展开描述,有兴趣的可以自行深入了解

Aspects

一个使用起来简单愉快的iOS AOP 库


(一)基本用法

+ (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;

AspectPositions

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            // 在原始实现后调用(default)
    AspectPositionInstead = 1,            // 将替换原始实现
    AspectPositionBefore  = 2,            // 在原始实现之前调用
    AspectOptionAutomaticRemoval = 1 << 3 // 执行一次后移除Hook
};

AspectInfo

@protocol AspectInfo <NSObject>
- (id)instance;

- (NSInvocation *)originalInvocation;

- (NSArray *)arguments;
@end

(二)Aspects 埋点实战

/** 统计页面的访问量 */
    [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo){
                                   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                       NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                     [self doSomething];
                                   });
                               } error:NULL];

(三)使用注意

  • Aspects 使用了Objective-C 的消息转发机会,会有一定的性能消耗,所有对于过于频繁的调用,不建议使用 Aspects
  • 当应用于某个类时(使用类方法添加钩子),不能同时hook父类和子类的同一个方法,否则会引起循环调用问题;但是当应用于某个类的示例时(使用实例方法加钩子),不受此限制。
  • 使用KVO时,最好在 aspect_hookSelector: 调用之后添加观察者,否则可能会引起崩溃