OC-runtime-防止按鈕多次點擊

按鈕重復點擊的情況主要有兩種,一種是用戶主動點擊,這個無法避免;二是網絡耗時造成的延遲情況,促使用戶重復點擊。


  • 場景一,用戶主動點擊,例如:重復的點贊和取消點贊的行為。

這種情況如果不進行處理,會造成頻繁訪問服務器的行為,造成服務器資源的浪費。

解決方法:對點擊事件進行延遲執行,再次點擊時,先取消之前的延遲事件,這樣的話,延遲時間內重復點擊的話,只會執行最后一次操作的事件。

   
 // 當按鈕再次點擊的時候,取消之前的延遲事件,執行新的延遲事件
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClickedAction:) object:sender];

    [self performSelector:@selector(buttonClickedAction:) withObject:sender afterDelay:1.0];


  • 場景二,網絡延遲時造成的延遲情況下,用戶再次點擊的行為。

這種情況存在的地方很多,所以進行所有的按鈕全局處理的方案,相對比較理想。

解決方法:使用runtime的方式,自定義方法替換button的事件響應方法sendAction,在自定義方法中,設置響應延遲,即是在延遲時間內只能響應一次事件,這樣等于限制了一定的時間單位內,只能執行一次事件。

點贊等場景,不適合該方式,要單獨處理.

實現方式:給UIButton添加一個分類,然后在分類中動態添加屬性

/*
 事件延遲時間
 */
@property NSTimeInterval acceptEventInterval;

/*
 事件接收時間
 */
@property NSTimeInterval acceptEventTime;

/*
 是否忽視事件延遲(特殊場景,不要需要延遲)
 */
@property Boolean isIgnoreEventInterval;
在.m中實現getter和setter方法,

#import <objc/message.h>

#define defaultTimeInterval 1.0  // 默認延遲1秒

static const char *AcceptEventInterval = "acceptEventInterval";
static const char *AcceptEventTime     = "acceptEventTime";
static const char *IsIgnoreEventInterval = "isIgnoreEventInterval";

@implementation UIButton (Interval)

- (NSTimeInterval)acceptEventInterval{
    
    return [objc_getAssociatedObject(self, &AcceptEventInterval) doubleValue];
}

- (void)setAcceptEventInterval:(NSTimeInterval)acceptEventInterval{
    
    objc_setAssociatedObject(self, &AcceptEventInterval, @(acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSTimeInterval)acceptEventTime{
    
    return [objc_getAssociatedObject(self, &AcceptEventTime) doubleValue];
}

- (void)setAcceptEventTime:(NSTimeInterval)acceptEventTime{
    
    objc_setAssociatedObject(self, &AcceptEventTime, @(acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setIsIgnoreEventInterval:(Boolean)isIgnoreEventInterval{
    objc_setAssociatedObject(self, &IsIgnoreEventInterval, @(isIgnoreEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (Boolean)isIgnoreEventInterval{
    return [objc_getAssociatedObject(self, &IsIgnoreEventInterval) boolValue];
}

+ (void)load{

   [super load];

    Method fromMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method toMethod = class_getInstanceMethod(self, @selector(xz_sendAction:to:forEvent:));
    
    // 使用自定義的sendAction方法替換系統的sendAction方法
    method_exchangeImplementations(fromMethod, toMethod);
}

- (void)xz_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    
    // 如果不需要事件延遲,則不進行延遲操作
    if(self.isIgnoreEventInterval == YES){
        
        [self xz_sendAction:action to:target forEvent:event];
        return;
    }
        
    // 這里處理一下,如果沒有達到事件延遲時間,則不執行
    NSTimeInterval now = [NSDate date].timeIntervalSince1970;
    
    self.acceptEventInterval = self.acceptEventInterval ? self.acceptEventInterval : defaultTimeInterval;
    
    if (now - self.acceptEventTime < self.acceptEventInterval) {
        return;
    }
    
    if (self.acceptEventInterval > 0) {
        self.acceptEventTime = now;
    }
    
    
    // 因為方法實現已經替換,調用xz_sendAction,就是調用sendAction的實現方法,所以不會造成死循環
    [self xz_sendAction:action to:target forEvent:event];
}
使用時,在需要的類中,引入這個頭文件,或者在.pch中引入;
  • 通過按鈕的isIgnoreEventInterval設置是否忽略按鈕事件延遲;
    btn.isIgnoreEventInterval = YES;
  • 通過按鈕的acceptEventInterval設置事件延遲的時間。
    btn.acceptEventInterval = 2;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容