一丶介绍
Aspect Oriented Programming(AOP),面向切面编程
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
[1] 比如我们最常见的就是日志记录了,举个例子,我们现在提供一个服务查询学生信息的,但是我们希望记录有谁进行了这个查询。如果按照传统的OOP的实现的话,那我们实现了一个查询学生信息的服务接口(StudentInfoService)和其实现类(StudentInfoServiceImpl.java),同时为了要进行记录的话,那我们在实现类(StudentInfoServiceImpl.java)中要添加其实现记录的过程。这样的话,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程。这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则。那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但却是背后日志记录对这些行为进行记录,并且查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的所在。AOP的编程,好像就是把我们在某个方面的功能提出来与一批对象进行隔离,这样与一批对象之间降低了耦合性,可以就某个功能进行编程。
---摘自百度百科
我们要用AOP来做什么呢?
1.搭建Control的时候,一般会写个BaseViewController,然后把相同功能的代码放在相同的函数内比如以下:
- (void)viewDidLoad
{
[super viewDidLoad];
//创建视图代码
[self createUI];
//初始化数据
[self initdata];
//网络请求
[self askNetwork];
}
规范代码,可以减少不同开发者之间沟通成本,以及提高问题的定位速度,减少解决时间;
鉴于BaseViewController太臃肿,有了aop编程,就有了新的解决方案;
二丶Aspects的介绍
OC 有一个成熟的aspect方案->Aspects
地址:https://github.com/steipete/Aspects
源码解析及应用:http://wereadteam.github.io/2016/06/30/Aspects/
主要还是用到oc神奇的runtime机制,动态的改变了 selector 和 IMP 的对应关系;(此图并非原创)
主要用到2个方法:
+ (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;
block 执行的时机
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.
};
三丶Aspect OC应用
@implementation UIViewController (Base)
+ (void)load
{
NSError *error = nil;
[self aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){
UIViewController *baseVc = [aspectInfo instance];
[baseVc createUI];
[baseVc initdata];
[baseVc askNetwork];
}error:&error];
if (error)
{
Log(@"Load error: %@",error);
}
}
@end
这么写成Categorys其实不用BaseViewcontrol也是可以的;只要导入#import "UIViewController+Base.h" 文件就可以;优化不是一点点;
四丶Swift 实现
首先要先知道几个东西;
1.swift 没有load方法,使用initialize()
2.@convention关键字的作用:
2.1 修饰 Swift 中的函数类型,调用 C 的函数时候,可以传入修饰过 @convention(c) 的函数类型,匹配 C 函数参数中的函数指针。
2.2 修饰 Swift 中的函数类型,调用 Objective-C 的方法时候,可以传入修饰过 @convention(block) 的函数类型,匹配 Objective-C 方法参数中的 block 参数
3.unsafeBitCast
unsafeBitCast是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在Swift的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么
4.需要把原先填写block的参数,转成AnyObject
具体代码:
override public class func initialize() {
/*
@convention
1. 修饰 Swift 中的函数类型,调用 C 的函数时候,可以传入修饰过 @convention(c) 的函数类型,匹配 C 函数参数中的函数指针。
2. 修饰 Swift 中的函数类型,调用 Objective-C 的方法时候,可以传入修饰过 @convention(block) 的函数类型,匹配 Objective-C 方法参数中的 block 参数
*/
let block: @convention(block) (AnyObject!) -> Void = {
info in
let aspectInfo = info as! AspectInfo
let control = aspectInfo.instance()
#需要判类,
if let myVc = control as? BaseViewController{
myVc.customView()
myVc.createUI()
myVc.askNetwork()
}
}
#block转AnyObject
let blobj: AnyObject = unsafeBitCast(block, to: AnyObject.self)
do {
let originalSelector = NSSelectorFromString("viewDidLoad")
#在viewDidLoad之后调用
try UIViewController.aspect_hook(originalSelector, with: .positionBefore, usingBlock: blobj)
} catch {
print("error = \(error)")
}
}