Objective-C:Runtime+ButtonClicked

Page content

一个优秀的解决方案,不仅高效,而且优雅

引言

在项目开发过程中,肯定遇到过按钮关联的方法有网络请求或者其他相类似的逻辑操作,这时会产生这样一种需求:避免按钮频繁点击造成的频繁逻辑处理(如频繁访问接口),以免造成服务器压力或其他不可预知的状况。

当然,解决问题的方式多种多样,举个最简单的例子:

~~~objective-c
- (IBAction)acceptBackOrder:(UIButton *)sender {
    [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(sellerVerifyBackOrderWithKind:) object:@(30)];
    [self performSelector:@selector(sellerVerifyBackOrderWithKind:) withObject:@(30) afterDelay:0.35];
}
~~~

通过在时段内取消上一次任务,可已解决这个问题,但是每一个需要这种操作的button方法都要这样写,造成冗余,却不够优雅。 那么,怎样优雅的解决这个问题呢?这就是这篇文章的正题:使用Runtime优雅的给Button添加连续点击限定。

实例

首先,给UIButton添加Category:UIButton+DelayEvent 接口文件如下:

~~~objective-c
@interface UIButton (DelayEvent)
@property (nonatomic, assign) NSTimeInterval delayInterval;//添加点击事件的间隔时间
@property (nonatomic, assign) BOOL delayIgnoreEvent;
@end
~~~

实现文件如下:

~~~objective-c
@implementation UIButton (DelayEvent)
/*
* Runtime 给Category添加属性
*/
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";
static const char *UIcontrol_ignoreEvent = "UIcontrol_ignoreEvent";

- (NSTimeInterval)delayInterval  {    
    return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}

- (void)setDelayInterval:(NSTimeInterval)delayInterval  {    
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(delayInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)delayIgnoreEvent  {    
    return [objc_getAssociatedObject(self, UIcontrol_ignoreEvent) boolValue];
}

- (void)setDelayIgnoreEvent:(BOOL)delayIgnoreEvent {    
    objc_setAssociatedObject(self, UIcontrol_ignoreEvent, @(delayIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

/*
* load 方法进行 Method Swizzling
*/
+ (void)load  {    
    // Tabbar以及ToolBar等特殊情况,添加一步判断,避免这类按钮点击闪退(把此功能局限于Button)
    if ([self respondsToSelector:@selector(delayIgnoreEvent)])  {    
        Method a = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method b = class_getInstanceMethod(self, @selector(__delay_sendAction:to:forEvent:));
        method_exchangeImplementations(a, b);
    }
}
/*
* 交换后的方法,便于我们控制
*/
- (void)__delay_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if (self.delayIgnoreEvent) return;
    if (self.delayInterval > 0)  {    
        self.delayIgnoreEvent = YES;
        [self performSelector:@selector(setDelayIgnoreEvent:) withObject:@(NO) afterDelay:self.delayInterval];
    }
    [self __delay_sendAction:action to:target forEvent:event];
}
~~~

附上参考的该解决方案的地址:http://www.cocoachina.com/ios/20150911/13260.html