直接上代码:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
//创建 UIButton的分类
@interface UIButton (WYRTapAction)
//是否忽略点击
@property (nonatomic, assign) BOOL ignoreEvent;
@end
NS_ASSUME_NONNULL_END
#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(@"忽略本次操作");
}
}
//因为在分类中 @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];
}
使用时候的代码
#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
相关使用 runtime 的应用场景:
iOS 运行时动态交换两个方法(Method-Swizzling)
iOS 通过runtime给分类添加动态属性
iOS 使用runtime动态添加方法
ios 使用runtime实现自动解归档