欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS AOP实战

程序员文章站 2022-06-14 09:45:53
AOP: 面向切面编程,偏向于处理业务的某个阶段 适用场景: 1. 参数校验:网络请求前的参数校验,返回数据的格式校验等等 2. 无痕埋点:统一处理埋点,降低代码耦合度 3. 页面统计:帮助统计页面访问量 4. 事务处理:拦截指定事件,添加触发事件 5. 异常处理:发生异常时使用面向切面的方式进行处 ......

aop: 面向切面编程,偏向于处理业务的某个阶段

适用场景:

  1. 参数校验:网络请求前的参数校验,返回数据的格式校验等等

  2. 无痕埋点:统一处理埋点,降低代码耦合度

  3. 页面统计:帮助统计页面访问量

  4. 事务处理:拦截指定事件,添加触发事件

  5. 异常处理:发生异常时使用面向切面的方式进行处理

  6. 热修复:aop可以让我们在某方法执行前后或者直接替换为另一段代码,我们可以根据这个思路,实现bug修复

  我们希望将以上需求分离到非业务逻辑的方法中,尽可能的不影响业务逻辑的代码。

demo 从配置aop到实际应用,记得给咱点个star~

源码分析

  0. 类说明

 

 mdaspectinfo:作为对象,包含调用信息(nsinvocation)的对象
         作为协议,提供访问对象的属性  mdaspectidentifier:包含一个hook的信息,调用者,时机,回调处理等
 mdaspecttracker:防止重复hook  mdaspectscontainer:通过runtime给被hook的对象添加属性,提供存储和移除hook的方法  mdaspecttoken:提供移除hook的协议

 

  1. hook时机

typedef ns_options(nsuinteger, mdaspectoptions) {
    mdaspectpositionafter   = 0,            /// 默认,当原方法执行完调用
    mdaspectpositioninstead = 1,            /// 替换原方法
    mdaspectpositionbefore  = 2,            /// 原方法执行前调用
    
    mdaspectoptionautomaticremoval = 1 << 3 /// will remove the hook after the first execution.
};

  2. 配置文件

配置hook的类,hook时机,实例方法和类方法,以及回调处理

为了区分实例方法和类方法,需要在类方法前加一个“+”

+(nsdictionary *)aop_mdviewcontrollerconfigdic{
    
    nsdictionary *configdic = @{
                                @"mdviewcontroller":@{//hook那个类名
                                        @"trackevents":@[
                                                @{//实例方法
                                                    @"moment":@"before",//hook之前调用
                                                    @"eventselectorname":@"instancemethod",//实例方法名
                                                    @"block":^(id<mdaspectinfo>aspectinfo){//回调处理
                                                        // 获取方法的参数
                                                        nslog(@"跳转");
                                                    },
                                                },
                                                @{//类方法
                                                    @"moment":@"instead",//替换原方法
                                                    @"eventselectorname":@"+hookclassmethod",//类方法名
                                                    @"block":^(id<mdaspectinfo>aspectinfo){//回调处理
                                                        // 获取方法的参数
                                                        nslog(@"到处可以hook到我");
                                                    },
                                                },
                                            ]
                                        },
                                };
    return configdic;
}

  3. 解析管理类  

// hook到方法回调,完全控制
typedef void (^aspecteventblock)(id<mdaspectinfo> aspectinfo);

@implementation mdaopmanager

+(void)load{ // 加载配置文件
    nsmutabledictionary *mutabledic = [nsmutabledictionary dictionary];
    [mutabledic addentriesfromdictionary:[mdaopmanager aop_mdviewcontrollerconfigdic]];
    [mutabledic addentriesfromdictionary:[mdaopmanager aop_mdsecviewcontrollerconfigdic]];
    [self configaopwithdic:mutabledic];
    
}

+(void)configaopwithdic:(nsdictionary *)configdic{
    // 解析配置文件
    for (nsstring *classname in configdic) {
        class clazz = nsclassfromstring(classname);//拿到类名
        nsdictionary *config = configdic[classname];//配置信息
        nsarray *trackarr = config[@"trackevents"];//方法数组
        if (trackarr) {
            for (nsdictionary *event in trackarr) {
                
                aspecteventblock buttonblock = event[@"block"];//回调
                nsstring *method = event[@"eventselectorname"];//方法名
                nsstring *moment = event[@"moment"];//hook时机
                
                mdaspectoptions option = mdaspectpositionafter;
                if ([moment isequaltostring:@"before"]) {
                    option = mdaspectpositionbefore;
                }else if ([moment isequaltostring:@"instead"]){
                    option = mdaspectpositioninstead;
                }
                
                sel selector = nsselectorfromstring(method);

                if ([method hasprefix:@"+"]) {//hook类方法
                    method = [method substringfromindex:1];
                    selector = nsselectorfromstring(method);

                    [clazz aspect_hookclassselector:selector withoptions:option usingblock:^(id<mdaspectinfo> aspectinfo) {
                        dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{
                            buttonblock(aspectinfo);
                        });
                    } error:null];
                }else{//hook实例方法
                    
                    [clazz aspect_hookselector:selector withoptions:option usingblock:^(id<mdaspectinfo> aspectinfo) {
                        dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{
                            buttonblock(aspectinfo);
                        });
                    } error:null];
                }
            }
        }
    }
}

4. 对外接口

// 类直接调用,hook实例方法
+ (id<mdaspecttoken>)aspect_hookselector:(sel)selector withoptions:(mdaspectoptions)options usingblock:(id)block error:(nserror **)error;
// 对象调用,hook实例方法
- (id<mdaspecttoken>)aspect_hookselector:(sel)selector withoptions:(mdaspectoptions)options usingblock:(id)block error:(nserror **)error;
// 类直接调用,hook类方法
+ (id<mdaspecttoken>)aspect_hookclassselector:(sel)selector withoptions:(mdaspectoptions)options usingblock:(id)block error:(nserror *__autoreleasing *)error;

// 对象调用,hook类方法
- (id<mdaspecttoken>)aspect_hookclassselector:(sel)selector withoptions:(mdaspectoptions)options usingblock:(id)block error:(nserror *__autoreleasing *)error;

总结

核心步骤:把目标selector的imp更换为runtime中的imp,从而直接进入消息转发,检查是否能添加hook,如果能,进行存储,接着方法交换处理,在消息转发里运行before instead after方法

说明:mdaspect是对aspects的扩展,添加了hook类方法的支持,希望能够帮助大家~