记录一次在继承关系中遇到methodswizzle坑的经历:
1、在Category中使用runtime的methodswizzle统一为UIButton添加点击延迟的功能:
使用对象关联为UIButton添加延时间隔(cs_acceptEventInterval)和事件触发时间(cs_acceptEventTime)两个运行时的关联属性,
使用methodswizzle用@selector(cs_sendAction:to:forEvent:)替换@selector(sendAction:to:forEvent:),并在@selector(cs_sendAction:to:forEvent:)使用了上述的两个关联属性
2、问题:
UIButton使用一切正常,但是这个category却影响到了UIControl这个控件了,点击UIControl后,会收到
'-[*** cs_acceptEventTime]: unrecognized selector sent to instance 0x10520ce60'
的奔溃信息。
3、原因:
3.1、关联对象添加的两个关联属性作用的是UIButton这个类
3.2、虽然在UIButton的扩展中替换了@selector(sendAction:to:forEvent:),但这个方法是从UIControl中继承过来的,methodswizzle实际上是作用在底层结构体执行的IMP实现,本质上是改了UIControl的@selector(sendAction:to:forEvent:),这种,当UIControl触发事件的时候,运行的是@selector(cs_sendAction:to:forEvent:)这个Selctor的IMP实现,但是UIControl又并没有两个时间属性的对象关联属性,所以这时候就报错了。
4、在UIControl扩展中添加延迟时间的功能,以下代码能正常运行:
#import <UIKit/UIKit.h>
@interfaceUIControl(FixMultiClick)
@property (nonatomic, assign) NSTimeInterval cs_acceptEventInterval; // 重复点击的间隔
@property (nonatomic, assign) NSTimeInterval cs_acceptEventTime;
@end
#import "UIControl+FixMultiClick.h"
#import <objc/runtime.h>
@implementationUIControl(FixMultiClick)
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";
- (NSTimeInterval)cs_acceptEventInterval {
return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}
- (void)setCs_acceptEventInterval:(NSTimeInterval)cs_acceptEventInterval {
objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(cs_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";
- (NSTimeInterval)cs_acceptEventTime {
return [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];
}
- (void)setCs_acceptEventTime:(NSTimeInterval)cs_acceptEventTime {
objc_setAssociatedObject(self, UIControl_acceptEventTime, @(cs_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 在load时执行hook
+ (void)load{
Methodbefore =class_getInstanceMethod(self,@selector(sendAction:to:forEvent:));
Methodafter =class_getInstanceMethod(self,@selector(cs_sendAction:to:forEvent:));
method_exchangeImplementations(before, after);
}
- (void)cs_sendAction:(SEL)actionto:(id)targetforEvent:(UIEvent*)event {
if ([NSDate date].timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_acceptEventInterval) {
DLog(@"___间隔时间太短");
return;
}
if (self.cs_acceptEventInterval > 0) {
self.cs_acceptEventTime = [NSDate date].timeIntervalSince1970;
}
[selfcs_sendAction:actionto:targetforEvent:event];
}
@end