• iOS 使用runtime 解决按钮重复点击的问题


    1、分析UIButton

    • 需要将按钮的点击事件方法和自定义方法在运行时动态交换。在自定义方法实现中,在间隔一段时间后设置按钮的点击事件中的操作有效。在间隔时间范围内的点击操作无效。从而达到解决按钮重复点击的效果。
    • UIButton 这个类本身是没有 sendAction:to:forEvent: 方法的,但是它继承自 UIControl,UIControl 有这个方法。
    • UIControl的子类包括:UIButton、UIDatePicker、UIPageControl、UISegmentedControl、UISlider、UIStepper、UISwitch。
    • 所以可以将父类 UIControl 的这个系统方法 - sendAction:to:forEvent: 作为切入点。

    2、使用runtime 解决按钮重复点击问题的错误方法

    • 方法一、直接将系统方法 sendAction:to:forEvent: 和自定义方法 wyr_sendAction:交换IMP。
    • 弊端:UIButton是没有sendAction:to:forEvent:方法的,直接交换IMP其实是和UIButton的父类,也就是UIControl交换了方法实现。那么当你使用UISwitch等UIControl的其他子类的时候,就会因为找不到fq_sendAction方法而崩溃。
    • 方法二、创建UIControl分类,自定义wyr_sendAction:方法,和系统的 sendAction:to:forEvent: 交换IMP。
    • 弊端: 这样做可以避免UIControl的子类调用崩溃,但是所有UIControl的子类都会走fq_sendAction方法,会导致事件不连续。

    3、正确解决方法

    直接上代码:

    #import <UIKit/UIKit.h>
    NS_ASSUME_NONNULL_BEGIN
    //创建 UIButton的分类
    @interface UIButton (WYRTapAction)
    //是否忽略点击
    @property (nonatomic, assign) BOOL ignoreEvent;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    #import "UIButton+WYRTapAction.h"
    #import <objc/runtime.h>  //引入 runtime 头文件
    
    #define EVENTINTERVAL 5 // 间隔时间
    @implementation UIButton (WYRTapAction)
    // load方法是应用程序把这个类加载到内存的时候调用,而且只会调用一次,所以在这个方法中实现方法的交换最合适
    +(void)load {
        //1、获取系统方法 sendAction:to:forEvent: 的 Method 信息 赋值给 sendEvent
        Method sendEvent = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        
        //2、获取自定义方法 wyr_sendAction:to:forEvent: 的 Method 信息 赋值给 my_sendEvent
        Method my_sendEvent = class_getInstanceMethod(self, @selector(wyr_sendAction:to:forEvent:));
        
        //3、使用 class_addMethod 给UIButton添加系统的 sendAction:to:forEvent:
        BOOL addsuccess = class_addMethod(self, @selector(sendAction:to:forEvent:), method_getImplementation(sendEvent), method_getTypeEncoding(sendEvent));
        
        if (addsuccess) {
            //4、添加成功说明UIButton没有这个方法的实现,那么 sendEvent 里面的class信息其实是父类UIControl的,这个时候我们再 class_getInstanceMethod 获取一遍就是 UIButton的 Method信息
            sendEvent = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        }
        //5、交换两个方法的 IMP(方法的实现函数)
        method_exchangeImplementations(sendEvent, my_sendEvent);
    }
    //自定义方法的实现
    - (void)wyr_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
        //如果此时点击不忽略
        if (!self.ignoreEvent) {
            //设置忽略点击
            self.ignoreEvent = YES;
            //调用自定义方法 (此时调用的UIButton的系统方法sendAction:to:forEvent:)
            [self wyr_sendAction:action to:target forEvent:event];
            //手动使用 performSelector 调用 setIgnoreEvent:方法,在间隔一段时间后设置 ignoreEvent 的值 为 NO:设置点击不忽略(本次点击的操作有效)
            [self performSelector:@selector(setIgnoreEvent:) withObject:@(NO) afterDelay:EVENTINTERVAL];
        } else {
            NSLog(@"忽略本次操作");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    //因为在分类中 @property 只有setter、getter的声明,没有setter、getter的实现 和 _变量。所以手动实现 setter 和 getter方法
    -(void)setIgnoreEvent:(BOOL)ignoreEvent {
        //使用 runtime 在 setter 方法中动态创建属性
        /**
             objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                                      id _Nullable value, objc_AssociationPolicy policy)
             1、object:保存到哪个对象中(给哪个对象的属性赋值)
             2、key:属性对应的 key
             3、value:设置属性值
             4、policy:使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择NONATOMIC
             */
        objc_setAssociatedObject(self, @selector(ignoreEvent), @(ignoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    -(BOOL)ignoreEvent {
        //动态获取属性
        // 此时 objc_getAssociatedObject 获取的 ignoreEvent 的值为 NSNumber类型,因此需要 如下操作转换成 BOOL类型的数据
        return [objc_getAssociatedObject(self, @selector(ignoreEvent)) boolValue];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用时候的代码

    #import "ViewController.h"
    //引入分类头文件
    #import "UIButton+WYRTapAction.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
        btn.center = CGPointMake([UIScreen mainScreen].bounds.size.width / 2, [UIScreen mainScreen].bounds.size.height / 2);
        [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:btn];
    }
    //在按钮点击的时候设置这个按钮不可点击,等待 多少秒延时后,再设置按钮可以点击;或者在操作技术的时候设置可以点击
    //-(void)btnClick:(UIButton *)sender {
    //    NSLog(@"点击按钮");
    //    sender.enabled = NO;
    //    for (int i = 0; i < 10000; i++) {
    //       // NSLog(@"i = %d",i);
    //        NSLog(@"按钮不可点击");
    //    }
    //    NSLog(@"循环执行完毕 - 按钮可点击了");
    //    sender.enabled = YES;
    //}
    //如果涉及到按钮不同状态不同样式的时候, 用enabled不见得够用.还得额外加个变量来记录状态.
    //如果全局解决按钮重复点击的问题,还是需要使用runtime 来解决。
    -(void)btnClick:(UIButton *)sender {
        NSLog(@"点击按钮");
        //sender.ignoreEvent = NO;
    }
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    相关使用 runtime 的应用场景:

    iOS 运行时动态交换两个方法(Method-Swizzling)
    iOS 通过runtime给分类添加动态属性
    iOS 使用runtime动态添加方法
    ios 使用runtime实现自动解归档

  • 相关阅读:
    洛谷P7529 Permutation G
    正向代理与反向代理
    【傻瓜式教程】Windows下安装Hive MySQL版【附安装Hadoop教程】全网最详细的图文教程
    从0开始学汇编的第三天:寄存器(内存访问)
    怎么将WPS转换成WORD?看完你就学会了
    【深度学习框架格式转化】【CPU】Pytorch模型转ONNX模型格式流程详解【入门】
    Docker简介
    网站ping端口的操作方法和命令介绍
    Swin transformer v2和Swin transformer v1源码对比
    Abaqus2019+VS2019+Fortran子程序安装关联全过程亲测有效
  • 原文地址:https://blog.csdn.net/same_life/article/details/126161218