AOP编程,就是切片编程,是切入到函数实现(函数代码块实现)的编程。问题场景:当你想在某个已存在函数代码块实现之前或之后,添加另一部分额外功能时,但又不想直接将代码插入(这部分代码相对独立,插进去总觉得和原来代码是逻辑无关的),也不想通过父类统一添加(有可能创建多个父类?);看样子,在编译阶段解决此问题,有些难度;在运行阶段有办法吗?有,就是AOP。
既然说的是OC中的AOP,就直说OC中如何实现的。
先说下原理:在OC中,不管类方法还是实例方法,”方法声明“用的是选择器类型记录的,即SEL类型,通常叫选择器名;方法都有自己的实现,像在c或c++中,方法名指向的就是函数入口(函数实现入口),OC不是这样的,他是通过选择器名索引到对应的实现,然后执行实现代码。
回到问题场景,如果我们能改变选择器名对应的实现地址,让他对应你那那段独立的代码实现,然后定义另外一个选择器名,让他对应原来那段代码实现,这样,程序运行时,就在不改变源代码的基础上完成了额外工作。需要注意的是,由于我们自己定义了新的选择器名,让他对应原来那段代码实现,所以在执行完额外功能后,需要在独立的代码实现后发送新的选择器消息。
项目中,拥有这中需求的类应该不止一个,所以我将交换函数实现的功能,放在NSObject分类中。这样哪个类需要,就在自己分类中的类方法发load中实现即可。下面举了UIViewController和UIControl俩个类的交换函数实现。
//
// NSObject+AOP.h
//
#import <Foundation/Foundation.h>
@interface NSObject (AOP)
/**
交换方法名对应的实现
@param newSelector 新方法名
@param oldSelector 老方法名
*/
+(void)exChangeMethodImpWithNewSelctor:(SEL)newSelector oldSelctor:(SEL)oldSelector;
@end
//
// NSObject+AOP.m
//
#import "NSObject+AOP.h"
#import <objc/runtime.h>
@implementation NSObject (AOP)
/**
交换方法名对应的实现(这样所有的NSObject子类都可以调用,谁调用self就是谁)
@param newSelector 新选择器方法名
@param oldSelector 老选择器方法名
*/
+(void)exChangeMethodImpWithNewSelctor:(SEL)newSelector oldSelctor:(SEL)oldSelector{
Method newMethod = class_getInstanceMethod([self class], newSelector);
Method oldMethod = class_getInstanceMethod([self class], oldSelector);
method_exchangeImplementations(newMethod, oldMethod);
}
@end
//
// UIViewController+AOP.h
//
#import <UIKit/UIKit.h>
@interface UIViewController (AOP)
@end
//
// UIViewController+AOP.m
//
#import "UIViewController+AOP.h"
#import "NSObject+AOP.h"
@implementation UIViewController (AOP)
//系统在加载类的时候,会自动调用load方法,且只调用一次。所以,在这里做aop操作。
+(void)load{
SEL newSelector = @selector(aop_viewWillAppear:);
SEL oldSelector = @selector(viewWillAppear:);
[self exChangeMethodImpWithNewSelctor:newSelector oldSelctor:oldSelector];
}
-(void)aop_viewWillAppear:(BOOL)animated{
NSLog(@"替换后是%s", __func__);
//调用原来实现
[self aop_viewWillAppear:animated];
}
@end
//
// UIControl+AOP.h
//
/*
iOS中大多数可点击UI控件都是UIControl的子类,而所有的事件也都要通过 - (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event转发,在这个方法里,我们也可以获取该控件所有的信息,包括所在的控制器,坐标系等,为配置埋点做依据。
这样我们就有了完美的swizzling对象了。通过该方法,(据我不完全统计)我们可以监听到UIButton、UITextField、UISwitch、UISegmentedControl、UISlider 、UIStepper等控件的Action事件。
*/
#import <UIKit/UIKit.h>
@interface UIControl (AOP)
@end
//
// UIControl+AOP.m
//
#import "UIControl+AOP.h"
#import "NSObject+AOP.h"
@implementation UIControl (AOP)
//系统在加载类的时候,会自动调用load方法,且只调用一次。所以,在这里最aop操作。
+(void)load{
SEL newSelector = @selector(aop_sendAction:to:forEvent:);
SEL oldSelector = @selector(sendAction:to:forEvent:);
[self exChangeMethodImpWithNewSelctor:newSelector oldSelctor:oldSelector];
}
- (void)aop_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
NSLog(@"替换后是%s", __func__);
NSLog(@"self class=%@---target class=%@", NSStringFromClass([self class]), NSStringFromClass([target class]));
//调用原来实现
[self aop_sendAction:action to:target forEvent:event];
}
@end