美文网首页
iOS中AOP面向切面编程SFAspect

iOS中AOP面向切面编程SFAspect

作者: samstring | 来源:发表于2020-06-10 23:41 被阅读0次

    面向切面编程

    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<SubClassModel *> *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<SubClassModel *> *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<SubClassModel *> *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

          本文链接:https://www.haomeiwen.com/subject/qhjcohtx.html