美文网首页tomiOS大咖iOS 开发随笔
iOS中利用AOP(面向切面)原理实现拦截者功能 超详细过程

iOS中利用AOP(面向切面)原理实现拦截者功能 超详细过程

作者: 程序汪 | 来源:发表于2017-02-01 16:40 被阅读1587次

    2018年5月9日更新

    最近有小伙伴在项目中集成了该框架,由于很久没有更新,该框架目前参数处理上会出很大问题,暂时无法解决,大家看这个就当学习一个思路。暂时不要拿到项目中使用。

    AOP简介

    • AOP: Aspect Oriented Programming 面向切面编程.
    • 我就不多数概念了,直接介绍这套框架吧.

    拦截者框架Interceptor

    • 想要对一些方法的参数进行监控
    • 监控后可以像日志一样的给我们
    • 程序运行时调用一个一个方法,我们在这些方法的前后插入方法,就像切面一样切进去插入拦截方法实现监控
    • 因为插入的拦截方法势必要打印日志,或者生成日志做本地化,如果加入主程序会污染代码,所以设计的时候,可以随时抽出,耦合度要非常低.

    框架逻辑介绍

    • 这个框架一定要对拦截者,被拦截者,拦截器,被拦截方法,拦截器替换的方法有着清晰的认识.
    • 拦截者,就是遵守了拦截协议的类,可以被拦截框架发现,同时拦截者还定义了被拦截者.这样拦截者就可以为被拦截者安装拦截器.为什么要有拦截器,因为被拦截者中的代码我们不能修改,我们只能在拦截者写上我们想要用来拦截的代码,拦截器就是用拦截者的代码插入或替换被拦截者中的方法.
    • 这个框架用到大量运行时的黑魔法,一些常用的自不必说,不常用的,我也添加了一些注释,跟着我的逻辑一步一步走看懂应该没什么问题
    • 下面给出逻辑图


      Interceptor框架逻辑图.jpg
    • 附上github源码github源码

    代码实现

    1.首先,拦截框架肯定是一个单例,专门处理拦截事物的,我用了一个单例宏,框架中用到的单例宏都是这样的

    #ifndef WBSingleton_h
    #define WBSingleton_h
    
    //定义单例模式类 INTERFACE_SINGLETON(类名)
    #undef  INTERFACE_SINGLETON
    #define INTERFACE_SINGLETON( __class) \
        - (__class *)sharedInstance; \
        + (__class *)sharedInstance;
    
    //实现单例模式类
    #undef  IMPLEMENTATION_SINGLETON
    #define IMPLEMENTATION_SINGLETON( __class) \
        - (__class *)sharedInstance \
        { \
            return [__class sharedInstance]; \
        } \
        + (__class *)sharedInstance \
        { \
            static dispatch_once_t once; \
            static __class * __singleton__; \
            dispatch_once( &once, ^{ __singleton__ = [[[self class] alloc] init]; } ); \
            return __singleton__; \
        } \
    
    #endif /* WBSingleton_h */
    

    2.拦截框架WBWInterceptor头文件内容

    #import <Foundation/Foundation.h>
    #import "objc/runtime.h"
    //单例头文件
    #import "WBSingleton.h"
    
    //动态关联对象需要用到的两个key
    #define kWBWInterceptorPropertyKey           @"kWBWInterceptorPropertyKey"
    #define kWBWnterceptedInstancePropertyKey   @"kWBWInterceptedInstancePropertyKey"
    
    //被拦截者方法宏,让拦截者添加被拦截者
    /**
     1.第一个方法,返回被拦截者的类
     2.set方法 运行时动态的为拦截者类关联一个被拦截者类属性,类似set方法
     3.get方法 运行时动态的为拦截者类关联一个被拦截者属性,类似get方法
     */
    #undef INTERCEPT_CLASS
    #define INTERCEPT_CLASS( __class ) \
    + (Class)interceptedClass \
    { \
    return [__class class]; \
    } \
    - (void)setInterceptedInstance:(__class *)instance \
    { \
    objc_setAssociatedObject(self, kWBWnterceptedInstancePropertyKey, instance, OBJC_ASSOCIATION_ASSIGN);\
    } \
    - (__class *)interceptedInstance \
    { \
    id interceptedInstance = objc_getAssociatedObject(self, kWBWnterceptedInstancePropertyKey); \
    return (__class *)interceptedInstance;\
    } \
    
    //拦截者协议,安装的时候会用运行时遍历所有的类,只有遵循了拦截者协议的类才能成为拦截者
    @protocol WBWInterceptorProtocol <NSObject>
    @end
    
    @interface WBWInterceptor : NSObject
    //单例声明
    INTERFACE_SINGLETON(WBWInterceptor)
    
    //主入口,安装
    + (void)setup;
    
    @end
    
    

    3.WBWInterceptor实现文件

    @implementation WBWInterceptor {
        //用来存放拦截者信息的字典,谁是拦截者,谁是被拦截者
        NSMutableDictionary     *_interceptorClasses;
    }
    //单例实现
    IMPLEMENTATION_SINGLETON(WBWInterceptor)
    
    //主入口,安装
    + (void)setup {
        [WBWInterceptor sharedInstance];
    }
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            _interceptorClasses = [NSMutableDictionary dictionary];
            //初始化拦截者类
            [self setupInterceptedClasses];
        }
        return self;
    }
    

    4.初始化中我们要找到所有的拦截者setupInterceptedClasses,同时遍历所有的拦截者,

    //初始化所有注入的类
    - (void)setupInterceptedClasses {
        //查询所有定义注入的类,就是所有遵循协议的类,拦截者类
        NSArray *interceptedClasses = [self queryInterceptorClasses];
        //遍历所有拦截者类
        [interceptedClasses enumerateObjectsUsingBlock:^(id  _Nonnull cls, NSUInteger idx, BOOL * _Nonnull stop) {
            //安装拦截器
            [self setupInterceptor:cls];
        }];
    }
    //获取所有的拦截者并返回拦截列表
    - (NSArray *)queryInterceptorClasses {
        NSMutableArray *interceptorClasses = [NSMutableArray array];
        int numClasses;
        Class *classes = NULL;
        classes = NULL;
        //通过objc_getClassList函数获取所有注册的类,文档提供的方法就是这么写的
        numClasses = objc_getClassList(NULL, 0);
        
        if (numClasses > 0) {
            //拦截者遵循的协议
            Protocol *aopProtocol = @protocol(WBWInterceptorProtocol);
            //获取一个所有类的存储空间,里面放了所有的类
            classes = (Class *)malloc(sizeof(Class) * numClasses);
            numClasses = objc_getClassList(classes, numClasses);
            //遍历所有类
            for (NSInteger i = 0; i < numClasses; i++) {
                //每一个类
                Class cls = classes[i];
                //遍历当前类本身和本身的所有父类
                for (Class thisClass = cls; nil != thisClass; thisClass = class_getSuperclass(thisClass)) {
                    //如果这个类遵循了协议,便是拦截者,便添加到拦截者列表中
                    if (class_conformsToProtocol(thisClass, aopProtocol)) {
                        [interceptorClasses addObject:cls];
                    }
                }
            }
            //释放,这是规矩
            free(classes);
        }
        //返回拦截者列表
        return interceptorClasses;
    }
    
    

    5.遍历所有拦截者,为被拦截者安装拦截器[self setupInterceptor:cls];

    
    typedef id (*InterceptedClassIMP) (id, SEL);
    //这个方法,会在拦截者中找到被拦截者,同时为被拦截者安装拦截器
    - (void)setupInterceptor:(Class)cls {
        //拦截者类中有一个宏定义方法 能够返回被拦截者类的方法
        //由于方法是宏定义出来的,所以这里会有一个警告:没有实现interceptedClass这个方法的警告,本人有强迫症,所以就用宏来忽略该警告了
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        SEL getInterceptedClassSel = @selector(interceptedClass);
    #pragma clang diagnostic pop
        
        //获得这个类方法,返回被拦截者类的类方法
        Method getInterceptedClassMethod = class_getClassMethod(cls, getInterceptedClassSel);
        //安全判断,如果拦截者没有去拦截,就返回
        if (getInterceptedClassMethod == NULL) {
            return;
        }
        //现在我们要知道被拦截者到底是什么类,我在上面定义了一个InterceptedClassIMP函数,用来得到被拦截者类的类
        InterceptedClassIMP getInterceptedClassMethodIMP = (InterceptedClassIMP)method_getImplementation(getInterceptedClassMethod);
        //这里我们终于拿到了被拦截者类,现在要给被拦截者类安装拦截器,实现拦截功能
        Class interceptedClass = getInterceptedClassMethodIMP(cls,getInterceptedClassSel);
        //为成员变量_interceptorClasses注册新的拦截者信息
        [self registerInterceptorClass:cls forInterceptedClass:interceptedClass];
        //为被拦截的类安装拦截器
        [self setupInterceptorClass:cls forInterceptedClass:interceptedClass];
        //实现拦截器功能,拦截目标类,替换成自己的方法
        [self interceptedMethodsWithInterceptedClass:interceptedClass interceptor:cls];
    }
    

    6.我们有了被拦截者,我们需要用一个成员变量保存被拦截者和拦截者之间的关系,这样之后我们知道被拦截者就可以通过成员变量获取它的拦截者.
    然后我们还需要为被拦截者安装拦截器,因为被拦截者中的代码我们是不能修改的,只能在拦截中写我们要插入或者修改的代码,拦截器可以动态的使用拦截者中的代码去修改插入被拦截者中的代码
    最后就是拦截器的核心代码了

    /**
     *  为成员变量_interceptorClasses注册新的拦截者信息
     *
     *  @param interceptor      拦截者类
     *  @param interceptedClass 被拦截的类
     *  用一个成员变量字典成对保存拦截者,被拦截者,方便以后调用
     */
    - (void)registerInterceptorClass:(Class)interceptor forInterceptedClass:(Class)interceptedClass {
        //kvc去赋值,拦截者字典中,被拦截者类:interceptedClass,拦截者是:interceptor
        [_interceptorClasses setObject:interceptor forKey:NSStringFromClass(interceptedClass)];
    }
    
    /**
     *  为被拦截的类安装拦截器
     *
     *  @param interceptedClass 被拦截者
     */
    //拦截器名
    #define GET_INTERCEPTOR_METHOD_NAME     @"interceptor"
    - (void)setupInterceptorClass:(Class)interceptor forInterceptedClass:(Class)interceptedClass {
        //利用运行时,动态添加一个方法,这个就是拦截器
        //这个方法我详细讲一下,现在我们有了被拦截的类,我们肯定要给它动态添加一个方法,这个方法就是拦截器
        //class_addMethod(Class cls, SEL name, IMP imp, const char *types)
        //cls:被添加方法的类
        //name:可以理解为方法名,我们这里用了一个宏定义GET_INTERCEPTOR_METHOD_NAME @"interceptor"
        //imp:实现这个方法的函数
        //types:一个定义该函数返回值类型和参数类型的字符串 根据该函数的格式(id)getInterceptorDynamicMethodIMP(<#id interceptedInstance#>, <#SEL _cmd#>),types应该写成"@@:",这个不理解自己查一下吧
        class_addMethod(interceptedClass, NSSelectorFromString(GET_INTERCEPTOR_METHOD_NAME), (IMP)getInterceptorDynamicMethodIMP, "@@:");
    }
    /**
     *  通过拦截器拦截目标类 核心方法
     *
     *  @param interceptedClass 被拦截者
     *  @param interceptor      拦截者
     */
    - (void)interceptedMethodsWithInterceptedClass:(Class)interceptedClass interceptor:(Class)interceptor {
        //终于到拦截器的写法了
        //利用运行时,找到被拦截者里面的所有方法
        NSArray *methods = [self methodsForClass:interceptedClass];
        //遍历被拦截者中的所有方法
        [methods enumerateObjectsUsingBlock:^(NSString* methodName, NSUInteger idx, BOOL * _Nonnull stop) {
            if (![methodName isEqualToString:@"interceptor"]) {
                //定义before方法
                SEL beforeMethodSel = NSSelectorFromString([NSString stringWithFormat:@"%@%@",INTERCEPTOR_BEFORE_METHOD_NAME,methodName]);
                //定义after方法
                SEL afterMethodSel  = NSSelectorFromString([NSString stringWithFormat:@"%@%@",INTERCEPTOR_AFTER_METHOD_NAME, methodName]);
                //从拦截者中获取before方法
                Method beforeMethod = class_getInstanceMethod(interceptor, beforeMethodSel);
                //从拦截者中获取after方法
                Method afterMethod  = class_getInstanceMethod(interceptor, afterMethodSel);
                //安全判断,看拦截者是否实现了这两个方法
                if (beforeMethod || afterMethod) {
                    //被拦截的方法的原始名
                    SEL originalMethodSel = NSSelectorFromString(methodName);
                    //新的名字
                    SEL newOriginalMethodSel = NSSelectorFromString([NSString stringWithFormat:@"%@%@", ORIG_METHOD_PREFIX, methodName]);
                    
                    Method originalMethod = class_getInstanceMethod(interceptedClass, originalMethodSel);
                    IMP origMethodIMP = class_getMethodImplementation(interceptedClass, originalMethodSel);
                    //为被拦截者类动态添加拦截方法
                    class_addMethod(interceptedClass, newOriginalMethodSel, origMethodIMP, method_getTypeEncoding(originalMethod));
                    //方法签名,NSMethodSignature,是对方法的参数,返回类型进行封装
                    NSMethodSignature *sig = [interceptedClass instanceMethodSignatureForSelector:originalMethodSel];
                    //利用方法签名获得返回类型
                    const char *returnType = sig.methodReturnType;
                    
                    //根据返回值不同,规定不同的方法
                    if(!strcmp(returnType, @encode(void)) ) {//返回值为空
                        //将被拦截者类中的被拦截方法替换成我们想要的方法
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)vCallbackDynamicMethodIMP ,method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(id))) {//返回值为对象
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)callbackDynamicMethodIMP ,method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(char))) {//返回值为char
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_char ,method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(unsigned char))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_unsigned_char,method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(signed char))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_signed_char,method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(unichar))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_unichar, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(short))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_short, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(unsigned short))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_unsigned_short, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(signed short))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_signed_short, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(int))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_int, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(unsigned int))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_unsigned_int, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(signed int))){
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_signed_int, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(long))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_long, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(unsigned long))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_unsigned_long, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(signed long))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_signed_long, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(long long))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_long_long, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(unsigned long long))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_unsigned_long_long, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(signed long long))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_signed_long_long, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(NSInteger))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_NSInteger, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(NSUInteger))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_NSUInteger, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(float))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_float, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(CGFloat))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_CGFloat, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(double))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_double, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(BOOL))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_BOOL, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(CGRect))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_CGRect, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(CGPoint))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_CGPoint, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(CGSize))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_CGSize, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(UIEdgeInsets))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_UIEdgeInsets, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(UIOffset))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_UIOffset, method_getTypeEncoding(originalMethod));
                    } else if(!strcmp(returnType, @encode(CGVector))) {
                        class_replaceMethod(interceptedClass, originalMethodSel, (IMP)CALLBACK_FUNCTION_NAME_CGVector, method_getTypeEncoding(originalMethod));
                    } else {
                        NSLog(@"not support return type ( %s ) in Class %@ => %@",method_getTypeEncoding(originalMethod),interceptedClass,methodName);
                    }            }
            }
        }];
    }
    /**
     *  运用运行时通过类获取所有方法
     *
     *  @param cls 类
     *
     *  @return 返回方法名的集合
     */
    - (NSArray *)methodsForClass:(Class)cls {
        NSMutableArray *methods = [NSMutableArray array];
        //安全判断
        if (cls == nil) return methods;
        uint methodListCount = 0;
        Method *pArrMethods = class_copyMethodList(cls, &methodListCount);
        //安全判断
        if (pArrMethods != NULL && methodListCount > 0) {
            for (int i = 0; i < methodListCount; i++) {
                SEL name = method_getName(pArrMethods[i]);
                NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
                [methods addObject:methodName];
            }
            free((void *)pArrMethods);
        }
        return methods;
    }
    

    7.三个方法的第一个不用我多说.说第二个方法,详细的注释也在上面,拦截器是一个方法,这个方法为被拦截者动态添加一个方法,将当前的拦截者和被拦截者动态关联起来,使拦截器可以使用拦截者中的方法替换插入到被拦截者中.这里就要用到存放拦截者信息的字典了

    @interface WBWInterceptor (PRIVATE)
    - (Class)interceptorClassForInterceptedClass:(Class)interceptedClass;
    @end
    //提供查询的方法
    - (Class)interceptorClassForInterceptedClass:(Class)interceptedClass {
        return [_interceptorClasses objectForKey:NSStringFromClass(interceptedClass)];
    }
    //动态添加拦截器的方法
    id getInterceptorDynamicMethodIMP(id interceptedInstance, SEL _cmd) {
        
        id interceptor = objc_getAssociatedObject(interceptedInstance, kWBWInterceptorPropertyKey);
        if(interceptor == nil) {
            //查询拦截者字典,根据被拦截者key找到拦截者
            Class interceptorClass = [[WBWInterceptor sharedInstance] interceptorClassForInterceptedClass:[interceptedInstance class]];
            //安全判断,拦截者存在
            if(interceptorClass != nil) {
                interceptor = [[interceptorClass alloc] init];
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
                //定义被拦截者中的宏set方法
                SEL setInterceptedInstanceSel = @selector(setInterceptedInstance:);
    #pragma clang diagnostic pop
                
                //安全判断,判断是否实现了set方法
                if([interceptor respondsToSelector:setInterceptedInstanceSel]) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                    //将拦截者对象和被拦截者对象动态关联起来
                    [interceptor performSelector:setInterceptedInstanceSel withObject:interceptedInstance];
    #pragma clang diagnostic pop
                }
                //将当前的拦截者和被拦截者动态关联起来
                objc_setAssociatedObject(interceptedInstance, kWBWInterceptorPropertyKey, interceptor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }
        }
        //返回拦截者
        return interceptor;
    }
    
    

    8.三个方法中的最后一个方法,拦截器的核心代码,我们找到被拦截者,遍历被拦截者中的所有方法,被拦截者中已经添加了一个拦截器方法,我们做判断,去掉这个方法,然后判断拦截者有没有提供before和after这两个方法,提供了,我们就开始替换,因为被拦截者中的方法,返回值不一定相同,所以我们要考虑所有情况,于是就有了上面那大段恶心的判断返回值的代码.根据不同的返回值使用不同的方法,然后又要写一堆方法,所以最后我采用了宏定义.

    /**
     *  无返回值调用
     *
     *  @param target 调用目标
     *  @param _cmd   调用方法
     *  @param ...    参数
     */
    void vCallbackDynamicMethodIMP(id target,SEL _cmd,...) {
        //处理方法的参数
        AOP_CREATE_INVOCATION(_cmd);
        execBeforeMethod(target, _cmd, invocation);
        execOrigMethod(target,_cmd,invocation);
        execAfterMethod(target, _cmd, invocation);
    }
    /**
     *  OC对象返回值调用
     *
     *  @param target 调用目标
     *  @param _cmd   调用方法
     *  @param ...    参数
     *
     *  @return 返回OC对象
     */
    id callbackDynamicMethodIMP(id target,SEL _cmd,...) {
        //处理参数
        AOP_CREATE_INVOCATION(_cmd);
        id returnValue = nil;
        execBeforeMethod(target, _cmd, invocation);
        execOrigMethod(target,_cmd,invocation);
        [invocation getReturnValue:&returnValue];
        execAfterMethod(target, _cmd, invocation);
        return returnValue;
    }
    
    //宏定义不同类型的返回值,不同的返回值调用不同的方法
    #undef AOP_DEF_TYPE_FUNCTION
    #define AOP_DEF_TYPE_FUNCTION( __type__ , __funcationName__ )                   \
    __type__ __funcationName__(id target,SEL _cmd,...) {                            \
    AOP_CREATE_INVOCATION(_cmd);                                                \
    execBeforeMethod(target, _cmd, invocation);                                 \
    execOrigMethod(target,_cmd,invocation);                                     \
    __type__ returnValue;                                                       \
    [invocation getReturnValue:&returnValue];                                   \
    execAfterMethod(target, _cmd, invocation);                                  \
    return returnValue;                                                         \
    }
    
    AOP_DEF_TYPE_FUNCTION(char,CALLBACK_FUNCTION_NAME_char)
    AOP_DEF_TYPE_FUNCTION(unsigned char,CALLBACK_FUNCTION_NAME_unsigned_char)
    AOP_DEF_TYPE_FUNCTION(signed char,CALLBACK_FUNCTION_NAME_signed_char)
    AOP_DEF_TYPE_FUNCTION(unichar,CALLBACK_FUNCTION_NAME_unichar)
    AOP_DEF_TYPE_FUNCTION(short,CALLBACK_FUNCTION_NAME_short)
    AOP_DEF_TYPE_FUNCTION(unsigned short,CALLBACK_FUNCTION_NAME_unsigned_short)
    AOP_DEF_TYPE_FUNCTION(signed short,CALLBACK_FUNCTION_NAME_signed_short)
    AOP_DEF_TYPE_FUNCTION(int, CALLBACK_FUNCTION_NAME_int)
    AOP_DEF_TYPE_FUNCTION(unsigned int, CALLBACK_FUNCTION_NAME_unsigned_int)
    AOP_DEF_TYPE_FUNCTION(signed int, CALLBACK_FUNCTION_NAME_signed_int)
    AOP_DEF_TYPE_FUNCTION(long, CALLBACK_FUNCTION_NAME_long)
    AOP_DEF_TYPE_FUNCTION(unsigned long, CALLBACK_FUNCTION_NAME_unsigned_long)
    AOP_DEF_TYPE_FUNCTION(signed long,CALLBACK_FUNCTION_NAME_signed_long)
    AOP_DEF_TYPE_FUNCTION(long long, CALLBACK_FUNCTION_NAME_long_long)
    AOP_DEF_TYPE_FUNCTION(unsigned long long, CALLBACK_FUNCTION_NAME_unsigned_long_long)
    AOP_DEF_TYPE_FUNCTION(signed long long, CALLBACK_FUNCTION_NAME_signed_long_long)
    AOP_DEF_TYPE_FUNCTION(NSInteger,CALLBACK_FUNCTION_NAME_NSInteger)
    AOP_DEF_TYPE_FUNCTION(NSUInteger, CALLBACK_FUNCTION_NAME_NSUInteger)
    AOP_DEF_TYPE_FUNCTION(float, CALLBACK_FUNCTION_NAME_float)
    AOP_DEF_TYPE_FUNCTION(CGFloat, CALLBACK_FUNCTION_NAME_CGFloat)
    AOP_DEF_TYPE_FUNCTION(double, CALLBACK_FUNCTION_NAME_double)
    AOP_DEF_TYPE_FUNCTION(BOOL,CALLBACK_FUNCTION_NAME_BOOL)
    AOP_DEF_TYPE_FUNCTION(CGRect,CALLBACK_FUNCTION_NAME_CGRect)
    AOP_DEF_TYPE_FUNCTION(CGPoint,CALLBACK_FUNCTION_NAME_CGPoint)
    AOP_DEF_TYPE_FUNCTION(CGSize,CALLBACK_FUNCTION_NAME_CGSize)
    AOP_DEF_TYPE_FUNCTION(UIEdgeInsets,CALLBACK_FUNCTION_NAME_UIEdgeInsets)
    AOP_DEF_TYPE_FUNCTION(UIOffset,CALLBACK_FUNCTION_NAME_UIOffset)
    AOP_DEF_TYPE_FUNCTION(CGVector,CALLBACK_FUNCTION_NAME_CGVector)
    
    

    9.然后在拦截者中要提供被监视方法的before和after方法,同时也要对参数进行处理,处理参数的宏定义我也给出了注释

    //原始方法名
    #define ORIG_METHOD_PREFIX              @"orig_"
    //前置拦截方法名
    #define INTERCEPTOR_BEFORE_METHOD_NAME  @"before_"
    //后置拦截方法名
    #define INTERCEPTOR_AFTER_METHOD_NAME   @"after_"
    
    //处理参数方法
    //这里我也讲一下
    //NSMethodSignature 方法签名
    //NSInvocation,和签名类似,但是参数没有限制,NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    //通过签名获取参数数量 NSUInteger argumentCount = [methodSignature numberOfArguments];
    //va_list va_start 这个就是对参数进行处理的宏,具体请自行查询
        //下面这个循环就是对所以参数设定位置,index必须从2开始,因为前两个被selector和target占用,,这样我们就插入了参数
    //for (int index = 2; index < argumentCount; index++) {
    //void *parameter = va_arg(arguments, void *);                                        \
    //[invocation setArgument:&parameter atIndex:index];                                  \
    //}
    #undef AOP_CREATE_INVOCATION
    #define AOP_CREATE_INVOCATION( __cmd ) \
    NSMethodSignature *methodSignature = [target methodSignatureForSelector:__cmd];          \
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];\
    va_list arguments;                                                                      \
    va_start(arguments, __cmd);                                                              \
    NSUInteger argumentCount = [methodSignature numberOfArguments];                         \
    for (int index = 2; index < argumentCount; index++) {                                   \
    void *parameter = va_arg(arguments, void *);                                        \
    [invocation setArgument:&parameter atIndex:index];                                  \
    }                                                                                       \
    va_end(arguments);
    
    //执行before方法
    void execBeforeMethod(id target,SEL _cmd,NSInvocation *invocation) {
        //方法名
        NSString *methodName = NSStringFromSelector(_cmd);
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        //获取拦截者类
        id interceptor = [target performSelector:@selector(interceptor)];
    #pragma clang diagnostic pop
    
        if(interceptor != nil) {
            SEL beforeMethodSel = NSSelectorFromString([NSString stringWithFormat:@"%@%@",INTERCEPTOR_BEFORE_METHOD_NAME,methodName]);
            if([interceptor respondsToSelector:beforeMethodSel]) {
                invocation.selector = beforeMethodSel;
                invocation.target = interceptor;
                [invocation invoke];
            }
        }
    }
    
    //执行after方法
    void execAfterMethod(id target, SEL _cmd, NSInvocation *invocation) {
        NSString *methodName = NSStringFromSelector(_cmd);
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        id interceptor = [target performSelector:@selector(interceptor)];
    #pragma clang diagnostic pop
    
        //callback after
        if(interceptor != nil) {
            SEL afterMethodSel = NSSelectorFromString([NSString stringWithFormat:@"%@%@",INTERCEPTOR_AFTER_METHOD_NAME,methodName]);
            if([interceptor respondsToSelector:afterMethodSel]) {
                invocation.selector = afterMethodSel;
                invocation.target = interceptor;
                [invocation invoke];
            }
        }
    }
    
    //执行原始方法
    void execOrigMethod(id target, SEL _cmd, NSInvocation *invocation) {
        SEL origSEL = NSSelectorFromString([NSString stringWithFormat:@"%@%@",ORIG_METHOD_PREFIX,NSStringFromSelector(_cmd)]);
        invocation.selector = origSEL;
        invocation.target = target;
        [invocation invoke];
    }
    

    实战演练

    实例代码1:简单的实现

    1.首先在appdelegate中安装,这样整个程序就具备了拦截功能,如果有一天不需要了,直接删除这段代码便可

    #import "AppDelegate.h"
    #import "WBWInterceptor.h"
    @interface AppDelegate ()
    @end
    @implementation AppDelegate
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        //安装
        [WBWInterceptor setup];
        return YES;
    }
    

    2.生成一个拦截者类,遵守拦截协议,实例代码就监控自带的ViewController了,所以名字就叫ViewController_log,就叫ViewController的拦截者,日志记录者,ViewController就是被拦截者

    #import <Foundation/Foundation.h>
    #import "WBWInterceptor.h"
    //遵循协议,成为拦截者
    @interface ViewController_log : NSObject <WBWInterceptorProtocol>
    
    @end
    

    3.实现拦截功能,在拦截者中设置被拦截者,提供拦截方法

    #import "ViewController_log.h"
    #import "ViewController.h"
    
    @implementation ViewController_log
    
    //设置被拦截类
    INTERCEPT_CLASS(ViewController)
    
    //实现拦截方法
    - (void)before_viewDidLoad {
        NSLog(@"before%s",__func__);
    }
    - (void)after_viewDidLoad {
        NSLog(@"after%s",__func__);
    }
    @end
    

    输出日志:

    可以看到在被拦截者ViewController中的viewDidLoad这个方法的前后我们个插入了一个拦截方法
    2017-01-31 17:13:18.859 WBW_AOP(线下bug处理)[15255:13398952] before-[ViewController_log before_viewDidLoad]
    2017-01-31 17:13:18.859 WBW_AOP(线下bug处理)[15255:13398952] after-[ViewController_log after_viewDidLoad]
    

    实例代码2:追踪参数变化,发现bug

    1.大家都知道,oc中崩溃大多因为空值在传递,使用这个框架就能监视所有的方法在执行时参数的值了.
    被拦截者代码

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSString *str= nil;
        [self loadDataWithStr:str];
    }
    - (void)loadDataWithStr:(NSString *)str {
        _testArr = @[str];
    }
    

    2.拦截者代码,自己随便写了点逻辑

    - (void)before_viewDidLoad {
        NSLog(@"before%s",__func__);
    }
    - (void)after_viewDidLoad {
        NSLog(@"after%s",__func__);
    }
    - (void)before_loadDataWithStr:(NSString *)str {
        NSString *warning = [NSString new];
        if (str == nil) {
            warning = @"警告:参数中有空值";
        }else {
            warning = @"参数安全";
        }
        
        NSLog(@"before%s%@%@",__func__,str,warning);
    }
    - (void)after_loadDataWithStr:(NSString *)str {
        NSString *warning = [NSString new];
        if (str == nil) {
            warning = @"警告:参数中有空值";
        }else {
            warning = @"参数安全";
        }
        NSLog(@"after%s%@%@",__func__,str,warning);
    }
    

    3.输出结果:可以看到只有两个before执行了,程序崩溃在loadDataWithStr中,原因是参数有空值

    2017-01-31 21:08:31.327 WBW_AOP(线下bug处理)[29826:13601061] before-[ViewController_log before_viewDidLoad]
    2017-01-31 21:08:31.328 WBW_AOP(线下bug处理)[29826:13601061] before-[ViewController_log before_loadDataWithStr:](null)警告:参数中有空值
    

    实例代码3

    1.现在我们明白了拦截者,就是去用自己定制的方法,去拦截被拦截者的方法,就像一个切面被切开,加入自己的方法,去监视方法中的参数,如果我们的日志都是用nslog,那就很low了,一个项目本身就有很多的测试nslog,加上我们这个框架,会乱.所以我们应该给拦截者写日志的能力,写好日志在本地化,或者发送给服务器,让我们在日志中查看程序的运行状态,出现的危险.
    2.我们用伪代码模拟一下用户登录,输入账号密码,然后在加密过程中出现错误,导致登录失败.

    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    //模拟点击登录
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"用户登录");
        [self encryptWithUserName:@"wbw" password:@"haha"];
    }
    //模拟加密过程 加密算法为字符串+encrypt
    - (void)encryptWithUserName:(NSString *)username password:(NSString *)password {
        NSLog(@"执行加密逻辑");
        username = [NSString stringWithFormat:@"%@+encrypt",username];
        //加密过程中的错误模拟
        password = nil;
        //调用登录
        [self loginWithUsername:username password:password];
    }
    //模拟登录过程
    - (void)loginWithUsername:(NSString *)username password:(NSString *)password {
        NSLog(@"执行登录逻辑");
        //判断
        [username isEqualToString:@"wbw+encrypt"] ? NSLog(@"账号正确") : NSLog(@"账号不正确");
        [username isEqualToString:@"haha+encrypt"] ? NSLog(@"密码正确") : NSLog(@"密码不正确");
    }
    

    3.然后我们用我们自定义的拦截者,可以判断参数安全.并做本地化

    #define INTERCEPTING_ORDER(num) \
    NSString *order = [NSString stringWithFormat:@"执行序列:%zd",num]; \
    
    #define INTERCEPTING_BEFORE_CONTENT(content) \
    NSMutableString *content = [NSMutableString new]; \
    [content appendString:[NSString stringWithFormat:@"%s开始执行.",__func__]] ; \
    
    #define INTERCEPTING_AFTER_CONTENT(content) \
    NSMutableString *content = [NSMutableString new]; \
    [content appendString:[NSString stringWithFormat:@"%s完成执行.",__func__]] ; \
    
    @implementation ViewController_log {
        NSInteger _num;
        NSMutableDictionary *_dic;
    }
    
    //设置被拦截类
    INTERCEPT_CLASS(ViewController)
    //存日志方法
    - (void)logWithContent:(NSString *)content forOrder:(NSString *)order {
        //写日志
        [_dic setValue:content forKey:order];
        //写入路径
        NSString*path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        //本地化
        [_dic writeToFile:[path stringByAppendingPathComponent:@"日志.plist"] atomically:YES];
    }
    //参数安全判断方法
    - (NSString *)judgeTheParameterWithFirst:(id)first andSecond:(id)second {
        NSMutableString *judgeMent = [NSMutableString new];
        first == nil ? [judgeMent appendString:@"警告:参数一为空."] : [judgeMent appendString:@"参数一安全."];
        second == nil ? [judgeMent appendString:@"警告:参数二为空."] : [judgeMent appendString:@"参数二安全."];
        return judgeMent;
    }
    
    //实现拦截方法
    - (void)before_viewDidLoad {
        _dic = [NSMutableDictionary dictionary];
        _num ++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_BEFORE_CONTENT(content);
    
        [self logWithContent:content forOrder:order];
    }
    - (void)after_viewDidLoad {
        _num++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_AFTER_CONTENT(content);
        [self logWithContent:content forOrder:order];
    }
    //拦截点击登录
    - (void)before_touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        _num ++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_BEFORE_CONTENT(content);
        NSString *judgeMent = [self judgeTheParameterWithFirst:touches andSecond:event];
        [content appendString:judgeMent];
        [self logWithContent:content forOrder:order];
    }
    - (void)after_touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        _num++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_AFTER_CONTENT(content);
        NSString *judgeMent = [self judgeTheParameterWithFirst:touches andSecond:event];
        [content appendString:judgeMent];
        [self logWithContent:content forOrder:order];
    }
    //拦截加密
    - (void)before_encryptWithUserName:(NSString *)username password:(NSString *)password {
        _num ++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_BEFORE_CONTENT(content);
        NSString *judgeMent = [self judgeTheParameterWithFirst:username andSecond:password];
        [content appendString:judgeMent];
        [self logWithContent:content forOrder:order];
    }
    - (void)after_encryptWithUserName:(NSString *)username password:(NSString *)password {
        _num++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_AFTER_CONTENT(content);
        NSString *judgeMent = [self judgeTheParameterWithFirst:username andSecond:password];
        [content appendString:judgeMent];
        [self logWithContent:content forOrder:order];
    }
    //拦截登录
    - (void)before_loginWithUsername:(NSString *)username password:(NSString *)password {
        _num ++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_BEFORE_CONTENT(content);
        NSString *judgeMent = [self judgeTheParameterWithFirst:username andSecond:password];
        [content appendString:judgeMent];
        [self logWithContent:content forOrder:order];
    }
    - (void)after_loginWithUsername:(NSString *)username password:(NSString *)password {
        _num++;
        INTERCEPTING_ORDER(_num)
        INTERCEPTING_AFTER_CONTENT(content);
        NSString *judgeMent = [self judgeTheParameterWithFirst:username andSecond:password];
        [content appendString:judgeMent];
        [self logWithContent:content forOrder:order];
    }
    

    4.程序运行,我们点击屏幕,开始登录,拦截者开始记录日志,并存入本地沙盒
    进入沙盒,找到 日志.plist 文件.打开,我们终于得到日志啦,这里演示的是本地化,我的拦截者日志功能写的很简陋,只是演示一下,自己可以写单例,就叫日志记录者,来管理所有的拦截者的日志,也可以写一个网络请求模块,将日志发送给服务器.


    日志2017-02-01 下午4.22.19.png

    5.根据日志我们瞬间能定位到,登录逻辑中,参数二就是密码为空了,说明传入的参数为空,我们很快就能定位到加密过程中,对密码的加密流程中出了错误.

    总结

    1.这个框架很小型,去掉所有的注释就400行,其实干的就一件事,找到一个被监视方法,在它前面插入一个方法,在它后面插入一个方法,实现监控
    2.我觉得还需要改进的就是拦截者逻辑,虽然自定义比较好,但是我们应该进一步封装,毕竟都懒,有那空写一个拦截者逻辑,不如自己慢慢找bug.所以我觉得这个框架主要是学习里面的方法,熟练运用运行时

    相关文章

      网友评论

      • 畅行天下游:你好,今天又来麻烦你解决问题了,今天发现在debug模式下通过拦截都能正常跑,但是打了hoc包之后,凡是有拦截的地方都会崩,请问这是什么原因啊?这个问题比较急,希望看到能尽快解惑,感激万分!:pray::pray::pray:
      • 畅行天下游:你好,我在自定义方法时加入参数,在拦截该方法时总是报Thread 1: EXC_BAD_ACCESS (code=1, address=0x5a101ce4487)的错误,在将参数传递给这个方法前参数都是有值的,但在拦截的时候参数似乎就被释放掉了,请问这是什么问题吗?
        程序汪:@畅行天下游 拦截方法不用给我看,给我看你传参,调这个showTopSegmentsName:(NSString *)name方法的代码,我看看问题出在哪
        畅行天下游:@程序汪

        INTERCEPT_CLASS(MSSmsViewController)

        - (void)before_showTopSegmentsName:(NSString *)name
        {
        NSLog(@"(MSSmsViewController_log) 频道切换前 name = %@", name);
        }

        - (void)after_showTopSegmentsName:(NSString *)name
        {
        NSLog(@"(MSSmsViewController_log) 频道切换后 name = %@", name);
        }

        你好,我并没有拦截带返回值的方法,拦截的都是带参数的方法,然而在拦截的before方法就会出现Thread 1: EXC_BAD_ACCESS 的崩溃。看了一下参数,在调用拦截方法之前参数还是有值的,在before方法中参数已经变成了nil了,是否这个参数对象被释放掉了呢?
        我用发通知的形式传递参数,通过通知参数对象直接赋值是可以传递的,但若是通知参数设置为一个字典,再从字典中取值,再将值赋值给拦截方法,这个时候before方法的参数值依然是nil,似乎还是被释放掉了。请问这个是什么问题呢?
        程序汪:详细代码给看一下呢。还有,这个拦截,不要去拦截有返回值的方法。
      • SegmetationFaul:厉害厉害!
      • 钟环:写的很好,收藏了
        程序汪:谢谢环儿支持~~~

      本文标题:iOS中利用AOP(面向切面)原理实现拦截者功能 超详细过程

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