AOP: 面向切面编程,偏向于处理业务的某个阶段
适用场景:
  1. 参数校验:网络请求前的参数校验,返回数据的格式校验等等
  2. 无痕埋点:统一处理埋点,降低代码耦合度
  3. 页面统计:帮助统计页面访问量
  4. 事务处理:拦截指定事件,添加触发事件
  5. 异常处理:发生异常时使用面向切面的方式进行处理
  6. 热修复:AOP可以让我们在某方法执行前后或者直接替换为另一段代码,我们可以根据这个思路,实现bug修复
  我们希望将以上需求分离到非业务逻辑的方法中,尽可能的不影响业务逻辑的代码。
demo 从配置AOP到实际应用,记得给咱点个star~
源码分析
  0. 类说明
 
-  MDAspectInfo:作为对象,包含调用信息(NSInvocation)的对象
 作为协议,提供访问对象的属性
-  MDAspectIdentifier:包含一个hook的信息,调用者,时机,回调处理等
 MDAspectTracker:防止重复hook
-  MDAspectsContainer:通过runtime给被hook的对象添加属性,提供存储和移除hook的方法
-  MDAspectToken:提供移除hook的协议
 
 
  1. hook时机
- typedef NS_OPTIONS(NSUInteger, MDAspectOptions) {
-     MDAspectPositionAfter   = 0,            /// 默认,当原方法执行完调用
-     MDAspectPositionInstead = 1,            /// 替换原方法
-     MDAspectPositionBefore  = 2,            /// 原方法执行前调用
-     
-     MDAspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
- };
 
  2. 配置文件
配置hook的类,hook时机,实例方法和类方法,以及回调处理
为了区分实例方法和类方法,需要在类方法前加一个“+”
- +(NSDictionary *)AOP_MDViewControllerConfigDic{
-     
-     NSDictionary *configDic = @{
-                                 @"MDViewController":@{//hook那个类名
-                                         @"TrackEvents":@[
-                                                 @{//实例方法
-                                                     @"moment":@"before",//hook之前调用
-                                                     @"EventSelectorName":@"instanceMethod",//实例方法名
-                                                     @"block":^(id<MDAspectInfo>aspectInfo){//回调处理
-                                                         // 获取方法的参数
-                                                         NSLog(@"跳转");
-                                                     },
-                                                 },
-                                                 @{//类方法
-                                                     @"moment":@"instead",//替换原方法
-                                                     @"EventSelectorName":@"+hookClassMethod",//类方法名
-                                                     @"block":^(id<MDAspectInfo>aspectInfo){//回调处理
-                                                         // 获取方法的参数
-                                                         NSLog(@"到处可以hook到我");
-                                                     },
-                                                 },
-                                             ]
-                                         },
-                                 };
-     return configDic;
- }
 
  3. 解析管理类  
- // hook到方法回调,完全控制
- typedef void (^AspectEventBlock)(id<MDAspectInfo> aspectInfo);
- @implementation MDAOPManager
- +(void)load{ // 加载配置文件
-     NSMutableDictionary *mutableDic = [NSMutableDictionary dictionary];
-     [mutableDic addEntriesFromDictionary:[MDAOPManager AOP_MDViewControllerConfigDic]];
-     [mutableDic addEntriesFromDictionary:[MDAOPManager AOP_MDSecViewControllerConfigDic]];
-     [self configAOPWithDic:mutableDic];
-     
- }
- +(void)configAOPWithDic:(NSDictionary *)configDic{
-     // 解析配置文件
-     for (NSString *className in configDic) {
-         Class clazz = NSClassFromString(className);//拿到类名
-         NSDictionary *config = configDic[className];//配置信息
-         NSArray *trackArr = config[@"TrackEvents"];//方法数组
-         if (trackArr) {
-             for (NSDictionary *event in trackArr) {
-                 
-                 AspectEventBlock buttonBlock = event[@"block"];//回调
-                 NSString *method = event[@"EventSelectorName"];//方法名
-                 NSString *moment = event[@"moment"];//hook时机
-                 
-                 MDAspectOptions option = MDAspectPositionAfter;
-                 if ([moment isEqualToString:@"before"]) {
-                     option = MDAspectPositionBefore;
-                 }else if ([moment isEqualToString:@"instead"]){
-                     option = MDAspectPositionInstead;
-                 }
-                 
-                 SEL selector = NSSelectorFromString(method);
-                 if ([method hasPrefix:@"+"]) {//hook类方法
-                     method = [method substringFromIndex:1];
-                     selector = NSSelectorFromString(method);
-                     [clazz aspect_hookClassSelector:selector withOptions:option usingBlock:^(id<MDAspectInfo> aspectInfo) {
-                         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-                             buttonBlock(aspectInfo);
-                         });
-                     } error:NULL];
-                 }else{//hook实例方法
-                     
-                     [clazz aspect_hookSelector:selector withOptions:option usingBlock:^(id<MDAspectInfo> aspectInfo) {
-                         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-                             buttonBlock(aspectInfo);
-                         });
-                     } error:NULL];
-                 }
-             }
-         }
-     }
- }
 
4. 对外接口
- // 类直接调用,hook实例方法
- + (id<MDAspectToken>)aspect_hookSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError **)error;
 
- // 对象调用,hook实例方法
- - (id<MDAspectToken>)aspect_hookSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError **)error;
 
- // 类直接调用,hook类方法
- + (id<MDAspectToken>)aspect_hookClassSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError *__autoreleasing *)error;
 
 // 对象调用,hook类方法
 
- - (id<MDAspectToken>)aspect_hookClassSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError *__autoreleasing *)error;
 
总结
核心步骤:把目标selector的IMP更换为runtime中的IMP,从而直接进入消息转发,检查是否能添加hook,如果能,进行存储,接着方法交换处理,在消息转发里运行before instead after方法
说明:MDAspect是对Aspects的扩展,添加了hook类方法的支持,希望能够帮助大家~