- 使用场景
在实际应用场景中,有几个业务场景需要控制UIButton响应事件的时间间隔。
- 当点击按钮来执行网络请求时,若请求耗时稍长,用户往往会多次点击。这样,就执行了多次请求,造成资源浪费。
- 在移动终端设备性能较差时,连续点击按钮会执行多次事件(比如push出来多个viewController)。
- 防止暴力点击。
- 解决3种方案:
1. 设置enabled或userInteractionEnabled属性(利用dispach_after方法)
//按钮第二次点击事件触发的等待时间
#define Button_Seconds_Time(_seconds_) \
static BOOL shouldPrevent; \
if (shouldPrevent) return; \
shouldPrevent = YES; \
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((_seconds_) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ \
shouldPrevent = NO; \
});
(void)tapBtn:(UIButton *)btn {
NSLog(@"按钮点击...");
btn.enabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
btn.enabled = YES;
});
}
2. 借助cancelPreviousPerformRequestsWithTarget:selector:object实现
- (void)tapBtn:(UIButton *)btn {
NSLog(@"按钮点击了...");
// 此方法会在连续点击按钮时取消之前的点击事件,从而只执行最后一次点击事件}
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClickedAction:) object:btn];
// 多长时间后做某件事情
[self performSelector:@selector(buttonClickedAction:) withObject:btn afterDelay:2.0];
}
- (void)buttonClickedAction:(UIButton *)btn {
NSLog(@"真正开始执行业务 - 比如网络请求...");
}
- 问题:会出现延时现象,并且需要对大量的UIButton做处理,工作量大,不方便。
3. 通过runtime交换方法实现
-
通过Runtime交换UIControl的响应事件方法,从而控制响应事件的时间间隔。
-
实现步骤如下:
- 创建一个UIControl的分类,使用runtime增加public属性sz_eventInterval和private属性sz_eventInvalid。
- 在+load方法中使用runtime将UIButton的-sendAction:to:forEvent:方法与自定义的sz_sendAction:to:forEvent:方法进行交换
- 使用sz_eventInterval作为控制sz_eventInvalid的计时因子,用sz_eventInvalid控制UIButton的event事件是否有效。
#import "UIControl+Extension.h"
#import <objc/runtime.h>
@interface UIControl()
/** 是否失效 - 即不可以点击 */
@property (nonatomic, assign) BOOL sz_eventInvalid;
@end
@implementation UIButton (Extension)
+ (void)load {
// 交换方法
Method clickMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method sz_clickMethod = class_getInstanceMethod(self, @selector(sz_sendAction:to:forEvent:));
method_exchangeImplementations(clickMethod, sz_clickMethod);
}
#pragma mark - click
- (void)sz_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
if (!self.sz_eventInvalid) {
self.sz_eventInvalid = YES;
[self sz_sendAction:action to:target forEvent:event];
[self performSelector:@selector(setSz_eventInvalid:) withObject:@(NO) afterDelay:self.sz_eventInterval];
}
}
#pragma mark - set | get
- (NSTimeInterval)sz_eventInterval {
return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
- (void)setSz_eventInterval:(NSTimeInterval)sz_eventInterval {
objc_setAssociatedObject(self, @selector(sz_eventInterval), @(sz_eventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)sz_eventInvalid {
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setSz_eventInvalid:(BOOL)sz_eventInvalid {
objc_setAssociatedObject(self, @selector(sz_eventInvalid), @(sz_eventInvalid), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}