iOS中AOP面向切面编程SFAspect

面向切面编程

AOP面向切面编程在后台开发中已经是一个老生常谈的话题了,如Spring这个框架是面向切面编程实现中尤为具有代表性的一个框架。

关于AOP的描述AOP_百度百科

这里的放一个关于切面作用的粗略图


iOS中面向切面.png

在iOS 中 可以通过面相切面编程做很多事情,如在业务代码的外埋点。脱离方法实现的参数检验,在dealloc前去释放资源等。

iOS中可以通过hook方法去实现AOP。基于需求,开发了一个库SFAspect,用于实现iOS中面向切面编程,实现参考了Aspect库。SFAspect支持以下功能

  • hook单个对象或类的所有实例对象
  • 设置优先级
  • 在hook方法中修改参数和返回值
  • 删除hook

SFAspect的使用

安装

pod 'SFAspect'

使用

  • hook单个对象实例方法
 [self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
        BOOL animated = NO;
        NSInvocation *invocation =  aspectModel.originalInvocation;
        //参数从2开始,因为方法执行的时候隐式携带了两个参数:self 和 _cmd,self是方法调用者,_cmd是被调用f方法的sel
        [invocation getArgument:&animated atIndex:2];
        NSLog(@"准备执行viewWillAppear,参数animated的值为%d",animated);
        //改变参数
        animated  = NO;
        [invocation setArgument:&animated atIndex:2];
    }];
    [self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"2" withPriority:0 withHookOption:(HookOptionAfter) withBlock:^(SFAspectModel *aspectModel, HookState state) {
           BOOL animated = NO;
           NSInvocation *invocation =  aspectModel.originalInvocation;
           //参数从2开始,因为方法执行的时候隐式携带了两个参数:self 和 _cmd,self是方法调用者,_cmd是被调用f方法的sel
           [invocation getArgument:&animated atIndex:2];
           NSLog(@"执行viewWillAppear后,参数animated的值为%d",animated);
        //也可以通过invocation获取返回值,详情参考消息转发过程中NSInvocation的用法
          
       }];
  • hook单个对象的类方法
 [self.vc hookSel:@selector(sayHiTo:withVCTitle:) withIdentify:@"3" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
        NSLog(@"hook单个对象的类方法");
    }];
  • hook类的所有对象的实例方法
 [SFHookViewController hookAllClassSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
       BOOL animated = NO;
       NSInvocation *invocation =  aspectModel.originalInvocation;
         [invocation getArgument:&animated atIndex:2];
        NSLog(@"准备执行viewWillAppear,参数animated的值为%d",animated);
        
    }];
  • hook类所有对象的类方法
 [SFHookViewController hookAllClassSel:@selector(sayHiTo:withVCTitle:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
       BOOL animated = NO;
       NSInvocation *invocation =  aspectModel.originalInvocation;
         [invocation getArgument:&animated atIndex:2];
       NSLog(@"hook所有对象的类方法");
        
    }];
  • hook同一个方法,优先级不同,优先级越高,越先执行
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {

          NSLog(@"准备执行viewWillAppear,执行的优先级是%d",aspectModel.priority);
          
      }];
    [self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"2" withPriority:1 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
        NSLog(@"准备执行viewWillAppear,执行的优先级是%d",aspectModel.priority);
                
        
    }];
  • 移除hook
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {

        NSLog(@"准备执行viewWillAppear,执行的优先级是%d",aspectModel.priority);
        
    }];
    //移除hook后hook里面的block不执行
    [self.vc removeHook:@selector(viewWillAppear:) withIdentify:@"1" withHookOption:(HookOptionPre)];
  • hook中 pre,after,around的区别
[self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"1" withPriority:0 withHookOption:(HookOptionPre) withBlock:^(SFAspectModel *aspectModel, HookState state) {
        //pre是在方法前执行
           NSLog(@"pre-准备执行viewWillAppear");
           
       }];
    [self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"2" withPriority:0 withHookOption:(HookOptionAfter) withBlock:^(SFAspectModel *aspectModel, HookState state) {
        //after是在方法前执行
        NSLog(@"after-执行viewWillAppear后");
        
    }];
    [self.vc hookSel:@selector(viewWillAppear:) withIdentify:@"3" withPriority:0 withHookOption:(HookOptionAround) withBlock:^(SFAspectModel *aspectModel, HookState state) {
        //around是在方法前后执行
           if(state == HookStatePre){
                 NSLog(@"around准备执行viewWillAppear");
           }
           if (state == HookStateAfter) {
                 NSLog(@"around-准备执行viewWillAppear");
           }
           
       }];

SFAspect实现原理

  • 实现原理
  • 对于单个对象的hook 和 类所有实例对象的hook
  • 什么时候释放动态生成的子类
  • 关于AspectContainer的释放

实现原理

对我们需要hook的方法,调用时强制使其进入消息转发流程。
大致流程如图


SFAspect实现原理.png

执行步骤如下:

1 对被hook的方法的原有实现存储起来(添加一个别名方法,并把实现存储到别名方法),把hook 需要添加的操作封装成一个SFAspectModel对象,并存储到类或元类(实例方法存储到类中,类方法存储到元类中)的关联对象SFAspectContainer中。SFAspectContainer的作用是存储一个类中所有的SFAspectModel,也就是存储所有的hook操作

2 将被hook的方法的实现设置为系统的转发函数_objc_msgForward,这样每一次调用被hook的方法的时候,都会进入消息转发流程。替换methodSignatureForSelector为自定义的方法sf_methodSignatureForSelector,forwardInvocation替换为自定义的sf_forwardInvocation方法。这样每次进入消息转发的流程就会调用自定义的sf_methodSignatureForSelector和sf_forwardInvocation函数

3执行到sf_forwardInvocation时候,在关联SFAspectContainer中取出被hook的方法对应的SFAspectModel对象。并根据HookOption执行SFAspectModel里面的block,也就是hook action中进行的操作。根据HookOption中是否存在HookOptionInstead去决定是否执行原有的方法实现(也就是调用步骤1中的别名方法)。


对于单个对象的hook 和 类所有实例对象的hook

在SFAspect中有两种hook。
一种是针对单个对象的hook,

-(BOOL)hookSel:(SEL)sel withIdentify:(NSString *)identify withPriority:(int)priority withHookOption:(HookOption)option withBlock:(HookBLock)block;

一种是针对类所有的对象的hook

+(BOOL)hookAllClassSel:(SEL)sel withIdentify:(NSString *)identify withPriority:(int)priority withHookOption:(HookOption)option withBlock:(HookBLock)block;

这两种hook的实现原理都是上述的那样,但是针对单个对象的hook,会动态生成一个子类。实现有点像KVO的实现。关系如下


SFAspect原理2-动态生成子类.png

然后对子类进行上述的3个步骤的操作。
对于单个对象hook,针对每个对象,都会动态生成一个子类。并把子类关联到元类的SubClass列表当中
对于针对所有对象的hook,是在原类上操作


什么时候释放动态生成的子类

上面提到,针对单个对象的hook,会动态生成一个子类,那这个子类在不需要用的时候,就应该销毁。销毁的时机有两个。
一个是在remove所有hook的时候,一个是在对象销毁的时候。

  • 对于remove所有hook这种情况,在每一个执行remove的时候都会检测是否已经remove了所有Hook,如果已经remove了hook,就会将对象的类重置为原类,并调用objc_disposeClassPair注销类
  if([NSStringFromClass([self class]) hasPrefix:SubClassPrefix]){
                if (!object_isClass(self)) {
                    //如果是调用者是对象,且不存在任何hook方法,则删除类
                    //如果调用者不是对象,只能等下一次hook的时候才能清除对象
                    if(isClassMethod)
                    {
                        //如果符合上面条件的是类方法,则检测对象方法是否有被hook,没有hook的情况下删除类
                        SFAspectContainer *classcontainer = getHookActionContainer([self class]);
                        if(classcontainer.preArray.count == 0 && classcontainer.afterArray.count == 0 && classcontainer.insteadArray.count == 0 && classcontainer.arroundArray.count == 0){
                            //清除父类中的关联对象
                            NSMutableArray *array =  objc_getAssociatedObject([self superclass], "subClassArray");
                              for (int i = 0; i < array.count; i++) {
                                  SubClassModel *subClass = array[I];
                                  if (subClass.subClass == [self class]) {
                                      [array removeObject:subClass];
                                      break;
                                  }
                              }
                            
                            Class class = [self class];
                            object_setClass(self, [self superclass]);
                            objc_disposeClassPair(class);
                        }
                    }else{
                        //如果符合上面条件的不是类方法,则检测类方法是否有被hook,没有hook的情况下删除类
                        SFAspectContainer *metaClasscontainer = getHookActionContainer(objc_getMetaClass(class_getName([self class])));
                        //                    SFAspectContainer *metaClasscontainer = getHookActionContainer([self class]);
                        if(metaClasscontainer.preArray.count == 0 && metaClasscontainer.afterArray.count == 0 && metaClasscontainer.insteadArray.count == 0 && metaClasscontainer.arroundArray.count == 0){
                            
                            //清除父类中的关联对象
                            NSMutableArray *array =  objc_getAssociatedObject([self superclass], "subClassArray");
                              for (int i = 0; i < array.count; i++) {
                                  SubClassModel *subClass = array[I];
                                  if (subClass.subClass == [self class]) {
                                      [array removeObject:subClass];
                                      break;
                                  }
                              }
                            
                            Class class = [self class];
                            object_setClass(self, [self superclass]);
                            objc_disposeClassPair(class);
                        }
                    }
                    
                    
                }
  • 对于一个是在对象销毁的去释放类这种情况,由于去监听一个对象释放后再去进行注销类的操作比较麻烦。所以把这一步操作放在了hookSel这个方法里面。在每一次hook的时候,都会调用
void clearNoUseSubClass(id object){
   NSMutableArray *array = nil;
   if([NSStringFromClass([object class]) hasPrefix:SubClassPrefix]){
       objc_getAssociatedObject([object superclass], "subClassArray");
   }
   else{
       objc_getAssociatedObject([object class], "subClassArray");
   }
   
   if (array == nil) {
       objc_setAssociatedObject([object class], "subClassArray", [NSMutableArray array], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
       return;
   }else{
       for (int i = 0; i < array.count; i++) {
           SubClassModel *model = array[I];
           if (model.object == nil && model.subClass != nil ) {
               [array removeObject:model];
               objc_disposeClassPair(model.subClass);
               i = i-1;
               
           }
           
       }
       
   }
   
}

这个函数的作用是检测一个原类是有有hookSel方法动态生成的子类,如果有,则检测子类对应的对象是否为空,如果为空,则销毁这个子类。
由上可知,如果不是通过removehook这种方式销毁子类,而是通过hookSel这种方式检测并销毁无用的子类的时候,内存中永远都会有一个动态生成的子类。但是由于动态生成的子类所占的内容空间几乎可以忽略不及,所以可以忽略。


关于AspectContainer的释放

关于AspectContainer这个类,这个类是用于存储关联hook的SFAspectModel的。由于是通过objc_setAssociatedObject方式去关联到类或是元类当中。所以当子类释放掉的时候,AspectContainer也会自动释放。对于原类的AspectContainer,会在执行remove掉所有的hook后会删除

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