iOS的AOP(面向切面)编程--Aspects

什么是AOP呢?下面是来自百科的一段话:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

综上所述:面向切面编程就是通过预编译和运行期动态代理实现给程序动态统一添加功能的一种技术。

我们什么情况下可能会用到AOP呢?比如如果需要在每个控制器的viewDidLoad里面都需要添加统计代码,或者每个类都需要添加日志代码。其实上面的需求很容易想到在每个控制器里面都写一遍,这样的话会有很多重复的代码而且不好维护。另外也可以用继承,但是用继承无形中增加了类的强耦合,所以都不是最好的办法。

这时可能很容易想到runtimemethod swizzlemethod swizzle是runtime的黑魔法之一,也就是在无法看到一个类的源代码的情况下,改变方法实现或者偷换方法实现的一种强大技术。method swizzle确实是一个很好的方法,而且降低了业务逻辑各个部分的耦合性。

Aspects

如果对method swizzle不太了解也没有关系,不过github上有比较成熟的第三方库,Aspects下载地址 。 Aspects就是基于method swizzle的第三方库,用的就是AOP面向切面编程思想。
库的核心代码就两个方法:

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id)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)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

上面两个方法就是实现对类的某些方法进行拦截。比如我们要在每个控制器的viewDidLoad方法中执行doSomethings方法。可以这样用:

 /**
  *  事件拦截
  *  拦截UIViewController的viewDidLoad方法
  */
  [UIViewController aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo)
  {
  //NSLog(@"viewDidLoad调用了 --- %@ --- %@ --- %@",aspectInfo.instance,aspectInfo.arguments, aspectInfo.originalInvocation);
   NSLog(@"%@ 对象的viewDidLoad调用了",aspectInfo.instance);

   /**
    *  添加我们要执行的代码,由于withOptions是AspectPositionAfter。
    *  所以每个控制器的viewDidLoad触发都会执行下面的方法
    */
    [self doSomethings];
  } error:NULL];

- (void)doSomethings
{
    //TODO: 比如日志输出、统计代码
    NSLog(@"------");
}

上面代码实现了拦截所有控制器的viewDidLoad事件。
1、aspect_hookSelector:表示要拦截指定对象的方法。
2、withOptions:是一个枚举类型,AspectPositionAfter表示viewDidLoad方法执行后会触发usingBlock:的代码。
3、usingBlock:就是拦截事件后执行的自定义方法。我们可以在这个block里面添加我们要执行的代码。

上面的代码运行可以看出,只要任何控制器对象或者其子类的viewDidLoad方法触发,doSomethings方法就会触发。

比如再举一个例子:

比如我们封装一个类似网易的菜单栏,效果如下图所示:

iOS的AOP(面向切面)编程--Aspects_第1张图片
菜单栏

如果实现上面的效果,首先创建一个类,比如名字是DLMenuView

iOS的AOP(面向切面)编程--Aspects_第2张图片
DLMenuView

DLMenuView.h里面我们定义一个类别,这个方法就是在外部做事件拦截用的,也就是当我们点击每个选项卡按钮的时候,会调用这个方法。如下所示:

@interface DLMenuView (Aspects)

- (void)dlMenuView:(DLMenuView *)menuView atIndex:(NSInteger)atIndex;

@end

DLMenuView.m里面定义如下:

@implementation DLMenuView (Aspects)

- (void)dlMenuView:(DLMenuView *)menuView atIndex:(NSInteger)atIndex
{
}

@end

上面实现不需要写任何代码,点击每个选项卡我们会调用这个方法,并且会传递2个参数:第一个是DLMenuView对象,第二个是点击的选项卡的索引atIndex。那不实现这个方法我们写有什么用呢?我们想下,拦截某个事件方法首先我们要告诉其他类哪些方法可以拦截,为了方便,我们使用类别。另外,我们总不能把按钮点击触发事件公开吧,这样设计的类也不是很合理。

当定义完成后,我们就可以侦听点击的是第几个选项卡了。我们可以在控制器里面这样用:

DLMenuView *menuView = [[DLMenuView alloc] initWithFrame:CGRectMake(0, 80, self.view.frame.size.width, 40)];
//菜单栏标题
menuView.menuTitles = @[@"新闻",@"头条",@"好声音",@"原创",@"视频",@"土豆",@"优酷",@"科技",@"图片"];
menuView.backgroundColor = [UIColor yellowColor];
//默认选择第1项
menuView.selectedIndex = 0;
[self.view addSubview:menuView];

 /**
  *  拦截menuView的dlMenuView:atIndex:方法
  */
[menuView aspect_hookSelector:@selector(dlMenuView:atIndex:) withOptions:AspectPositionAfter usingBlock:^(id aspects, DLMenuView *menuView, NSInteger index)
{
     NSLog(@"按钮点击了 %ld",index);
} error:nil];

上面就实现了侦听dlMenuView:atIndex:的调用,拦截这个方法后获取到的index就是点击的选项卡的索引。

上面方法可以减少类与类的耦合度,我们可以在不设置回调的情况下就能很方便的满足需求。有时候,回调很多或者嵌套对我们处理某些问题带来很多麻烦。总之我们采用的任何处理方式或者设计模式等方案,都需要考虑尽量减少类与类之间的耦合,使类更好用

Aspects确实是一个很强大的类,特别对于有添加日志、统计等需求项目来说带来了方便,就不需要我们在每个类里面添加相同的代码,因为添加的这些代码与当前类的业务关联不是很大。

最后附上Demo: 下载地址

你可能感兴趣的:(iOS的AOP(面向切面)编程--Aspects)