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

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.所以我觉得这个框架主要是学习里面的方法,熟练运用运行时

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,454评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 看着文字 突然翻身 手机随手不知道掉哪里了 当我闭上眼睛时 窗外电子琴的声音穿进我的耳膜 久远而熟悉的东方红弹奏 ...
    田萍阅读 256评论 2 10
  • 昨天说要读书,今天便真读了;说一天读一本,今天真读了一本。该休息,带孩子,洗衣服,家务全没有耽误。真的太棒...
    亡羊补牢君阅读 254评论 0 0
  • 小朋友的遣词造句就是给我很多惊喜。小朋友,你的意思是说你的心里都是我么?太感谢啦! 但是你还不知道在大人的世界里,...
    宝艾妈妈阅读 293评论 0 0