• iOS AOP实战


    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;

     

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

  • 相关阅读:
    Server-Sent Events(SSE) 简单实现和避坑
    使用ZIP进行多文件保存和读取(JDK1.7之后ZipOutputStream已经支持中文)
    IO
    页面临时添加a元素来模拟上传下载
    Centos 7启动jar包的详细步骤
    Redis 下载地址
    永久关闭win10自动更新
    同一命名空间下,无法引用类时
    Web应用程序项目******已配置为使用IIS。未能找到Web服务器
    VS2012打开项目 提示Asp.net4.0未在web服务器上注册的解决方案
  • 原文地址:https://www.cnblogs.com/lyjpost/p/11402024.html
Copyright © 2020-2023  润新知