首先理解下几个概念
1、IMP:它是指向一个方法具体实现的指针,每一个方法都有一个对应的IMP,当你发起一个消息之后,最终它会执行的那段代码,就是由IMP这个函数指针指向了这个方法实现的
2、SEL:方法名称的描述,只记录方法的编号不记录具体的方法,具体的方法是 IMP
3、Method:是一个类实例,里面的结构体有一个方法选标 SEL – 表示该方法的名称,一个types – 表示该方法参数的类型,一个 IMP - 指向该方法的具体实现的函数指针。
针对UIButton、UISegmentedControl、UISwitch这些继承自UIControl的控件可通过hook sendAction:to:forEvent:这个方法实现控件的重复点击
具体思路:
1、创建UIButton的类别,使用runtime添加public属性eventInterval作为计时因子
2、添加private属性eventUnavailable来控制button的点击事件是否有效
3、在+load方法中实现系统sendAction:to:forEvent:方法与自定义方法进行交换
代码实现:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method origMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
SEL origsel = @selector(sendAction:to:forEvent:);
Method swizMethod = class_getInstanceMethod([self class], @selector(tk_sendAction:to:forEvent:));
SEL swizsel = @selector(tk_sendAction:to:forEvent:);
BOOL addMehtod = class_addMethod([self class], origsel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
if (addMehtod) {
class_replaceMethod([self class], swizsel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, swizMethod);
}
});
}
- (void)tk_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
if (self.eventUnavailable == NO) {
self.eventUnavailable = YES;
[self tk_sendAction:action to:target forEvent:event];
[self performSelector:@selector(setEventUnavailable:) withObject:@(NO) afterDelay:self.eventInterval];
}
}
针对单击事件可对UITapGestureRecognizer的initWithTarget:action:或addTarget:action:进行hook
具体思路:
1、创建UITapGestureRecognizer的类别,使用runtime添加public属性eventInterval作为计时因子
2、添加private属性eventUnavailable来控制button的点击事件是否有效
3、在+load方法中实现系统initWithTarget:action:方法与自定义方法进行交换
4、将target和selector关联到创建的类别中并且将selector替换成类别中自定义的响应方法
代码实现:
- (instancetype)initTKWithTarget:(id)target action:(SEL)action {
self = [self initTKWithTarget:self action:@selector(tap:)];
objc_setAssociatedObject(self, gestureTargetKey, target, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, gestureSelKey, NSStringFromSelector(action), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return self;
}
- (void)tap:(UIGestureRecognizer *)tapGesture {
id target = objc_getAssociatedObject(self, gestureTargetKey);
SEL action = NSSelectorFromString(objc_getAssociatedObject(self, gestureSelKey));
if (self.eventUnavailable == NO) {
self.eventUnavailable = YES;
[target performSelector:action];
[self performSelector:@selector(setEventUnavailable:) withObject:@(NO) afterDelay:self.eventInterval];
}
}
针对单击事件的第二种实现方式实现带参数的init方法
具体思路:
1、在分类中实现initWithTarget:action:eventIntervl:的方法通过传进去计时因子控制点击事件是否可以响应,将手势的代理设置成新建的分类
2、添加属性eventUnavailable来控制点击事件是否有效
3、通过重写gestureRecognizer:shouldReceiveTouch:来控制点击事件是否响应
代码实现:
-(instancetype)initWithTarget:(id)target action:(SEL)action eventIntervl:(NSTimeInterval)eventIntervl {
self = [super init];
if (self) {
self.eventInterval = eventIntervl;
self.delegate = self;
[self addTarget:target action:action];
}
return self;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if (self.eventUnavailable == NO) {
self.eventUnavailable = YES;
[self performSelector:@selector(setEventUnavailable:) withObject:@(NO) afterDelay:self.eventInterval];
return YES;
} else {
return NO;
}
}