iOS 解决按钮防重复点击的问题

发布时间 2023-03-29 19:52:36作者: qqcc1388

日常使用中经常会出现按钮重复点击导致的数据重复提交问题,从而导致数据出错,常用的解决办法有
1、在发起请求的时候来一个全屏的loading这样在loading期间按钮就无法被点击,这种方式有个弊端就是loading弹窗起来需要几百毫秒时间左右,在这段时间期间用户如果手速过快,仍然可以触发多次点击事件;
2、发起提交时把按钮enabled或者userInteractionEnabled设置为false,然后提交完成或者返回错误时再把数据恢复,这种方法需要时刻记住按钮的使能状态,如果接口调用业务比较复杂,可能会导致某个场景未设置使能状态,从而导致按钮无法点击的情况;
3、按钮点击后存一下点击时间,按钮再次点击时把当前时间和上次时间比较,做一个时间差的换算,这样也确实可以解决问题,但是呢,每次都要去存时间和取时间在算时间,多少有点麻烦了,当然可以把这个功能写成一个分类来实现,该方法确实能解决问题,也是目前很多人都在使用的方案;
4、使用cancelPreviousPerformRequestsWithTarget:和performSelector来实现,通过该方法可以实现在指定时间范围内,如果重复点击,只会执行一次,本例中将使用该方案实现,当然该方案也有一定的弊端,就是永远只有最后一次按钮点击会被执行,这样就可能导致从按钮点击到触发事件可能会有时间延迟,本着学习的态度,探索一下performSelector的底层实现,本例采用该方案。

1、创建UIButton分类,并给分类添加属性interval,通过objc_setAssociatedObject和objc_getAssociatedObject来存储成员变量,该值用来做多次点击时间间隔,默认为0,interval为0的情况下走默认按钮点击事件,不处理多次点击事件

-(void)setInterval:(NSTimeInterval)interval{
    objc_setAssociatedObject(self, &kIntervalkey, @(interval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSTimeInterval)interval{
    NSNumber *number = objc_getAssociatedObject(self, &kIntervalkey);
    return number.doubleValue;
}

相关参数介绍:
key:要保证全局唯一,key与关联的对象是一一对应关系。必须全局唯一。
value:要关联的对象。
policy:关联策略。有五种关联策略。
OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。
OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。
OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。
OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。
OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)。

2、重写+load方法,通过runtime交换UIButton的sendAction:to:forEvent:事件,UIButton的addTarget事件最终都会走这个方法

+(void)load{
    Class class = [self class];

    SEL originalSelector = @selector(sendAction:to:forEvent:);
    SEL swizzledSelector = @selector(swizzled_sendAction:to:forEvent:);
    
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if (success) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

注意 这里一定要先class_addMethod再执行交换操作,否则可能会导致[super class]调用交换方法时导致找不到方法crash
详细比较class_addMethod/class_replaceMethod/method_exchangeImplementations以及对应的区分和示例,可以查看该作者分享的内容,讲解的特别好,并且有详细的例子分析

https://www.jianshu.com/p/ccb75e5277b7