美文网首页
1.通过运行时给NSObject 增加一个Bool 属性 并将其

1.通过运行时给NSObject 增加一个Bool 属性 并将其

作者: 严兵胜 | 来源:发表于2017-11-27 13:14 被阅读0次
    • 问题描述:
      笔者在实际开发中遇到了一个开发需求 就是给项目中所有的 UIControl控件 (凡是能够通过 addTarget: 来添加交互的控件都是 UIControl控件) 比如按钮的 交互点击做全局埋点 也就是说要拦截按钮的点击活动. 然后也要提供一个参数 拦截到点击后是否需要将该埋点数据上报给后端.

    • 问题解决:
      1.对于点击拦截问题 笔者提供了一个类拓展,直接将系统的sendAction:to:from:forEvent:进行了替换
      2.对于是否需要上报 笔者也是给NSObject 拓展了一个BOOL 属性 (NSObject 是所有控件的最终父类 给NSObject 拓展属性 外界所有的控件都可以访问到)

    点击拦截
    • 说明:
      拦截所有 UIControl控件 的点击行为 --- 方法交换 给UIApplication 拓展方法

    .m中

    #import "UIApplication+Hook.h"
    #import <objc/runtime.h> // 使用运行时需要包含此
    #import "UIViewController+Extension.h" // 获取目前在栈顶的控制器 一般也就是目前正在显示的控制器
    
    @implementation UIApplication (Hook)
    
    #pragma mark - 点击拦截
    + (void)load{
    
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 获取原方法 及 新增方法的 函数名 (这里有个isa 指针 以及函数列表 和函数实现的知识 笔者在这里就不解释了)
            SEL originalSelector = @selector(sendAction:to:from:forEvent:);
            SEL swizzledSelector = @selector(wsd_sendAction:to:from:forEvent:);
            [self swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
    
        });
    
    }
    
    + (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
    {
        Class class = cls;
        // 获取原方法 及 新增方法的 函数实现  (这里有个isa 指针 以及函数列表 和函数实现的知识 笔者在这里就不解释了)
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
        BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
    
        if (didAddMethod) {
            class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    
    
    - (BOOL)wsd_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event{
        
        NSObject *senderObj = sender;
        NSString *targetClassName = [NSString stringWithFormat:@"%@", [target class]];
        // 拒绝所有关于 UITabBar 的点击响应的统计
        if ([targetClassName isEqualToString:@"UITabBar"] || [targetClassName isEqualToString:@"TDAAUIControlBinding"] || [targetClassName isEqualToString:@"WSDTabBarViewController"]) {
            
        }else{ // 加入统计
            WSDLog(@"------------------ 监听到点击 -------------------   \n响应事件名称 = %@ ; \n第一响应者 = %@ ; \n第一响应者所在的控制器 = %@  \n是否需要上报 = %@",NSStringFromSelector(action),[target class] ,[[self.keyWindow.rootViewController wsd_topViewController] class],senderObj.wsd_haveAnalyzeBool? @"YES" : @"NO")
        }
        
        return [self wsd_sendAction:action to:target from:sender forEvent:event];
        
    }
    
    @end
    
    解释:

    // 获取原方法 及 新增方法的 函数名 (这里有个isa 指针 以及函数列表 和函数实现的知识 笔者在这里就不解释了)

    • SEL originalSelector = @selector(sendAction:to:from:forEvent:);
      // 获取原方法 及 新增方法的 函数实现
    • Method originalMethod = class_getInstanceMethod(class, originalSelector);
      // 如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
    • BOOL didAddMethod = class_addMethod()
      // 如果返回成功:则说明被替换方法没有存在.也就是被替换的方法没有被实现,我们需要先把这个方法实现,然后再执行我们想要的效果,用我们自定义的方法去替换被替换的方法. 这里使用到的是class_replaceMethod这个方法. class_replaceMethod本身会尝试调用class_addMethod和method_setImplementation,所以直接调用class_replaceMethod就可以了)
    • if (didAddMethod) {
      class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
      } else {// 如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换即
      method_exchangeImplementations(originalMethod, swizzledMethod);
      }
      该方法一定要写 不然外界拿不到点击事件 我们只是半路拦截 拦截之后 的事情还是交个系统处理
    • return [self wsd_sendAction:action to:target from:sender forEvent:event];
    全局新增Bool 属性 并设置初始值为YES
    • 说明:
      给NSObject 拓展一个全局的Bool类型的 属性
      .h中


      Snip20171127_1.png

      .m中


      Snip20171127_2.png
    解释
    • 大部分东西没什么可说的 就是通过运行时 拓展一个bool 属性 这里要说的是 如何实现Bool 的初始值为YES
      关键代码
    • 在你完成运行时代码之后 可能会收到一个关于这个的警告
      Property '你的属性名' requires method '你的属性名:' to be defined - use @dynamic or provide a method implementation in this category Snip20171127_4.png
      这是因为我们在类拓展中 增加属性时 系统是不会给我们自动生成setter方法和getter方法 在我们@property新增属性时会有来个对应词 一个是@synthesize,一个是@dynamic如果@synthesize@dynamic都没写,那么默认的就是@syntheszie var = _var;

    @synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

    @dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)
    假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;
    或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃
    编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定

    消除警告
    @dynamic 你增加的属性; 在.中就可以了

    • 设置bool 默认值为YES

    在实现getter方法中 直接使用以下代码就可以了

    id objc = objc_getAssociatedObject(self, &增加该属性的静态key);
        if (objc) return [objc boolValue];
        
        self.新增属性= true; // 设置默认值为yes
        return self.新增属性;
    

    相关文章

      网友评论

          本文标题:1.通过运行时给NSObject 增加一个Bool 属性 并将其

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