解決iOS由於網絡卡頓點擊button或者cell屢次push的bug

 

需求

公司的內網測試環境由於網絡作過了限制,比較卡,因此測試連續點擊button或者cell時可能會屢次push控制器.如何在代碼改動範圍最小的範圍內來解決這個問題呢?git

方法:防止按鈕重複暴力點擊

程序中大量按鈕沒有作連續響應的校驗,連續點擊出現了不少沒必要要的問題,例如發表帖子操做,用戶手快點擊屢次,就會致使同一帖子發佈屢次。github

#import <UIKit/UIKit.h>
//默認時間間隔
#define defaultInterval 1
@interface UIButton (Swizzling)
//點擊間隔
@property (nonatomic, assign) NSTimeInterval timeInterval;
//用於設置單個按鈕不須要被hook
@property (nonatomic, assign) BOOL isIgnore;
@end
#import "UIButton+Swizzling.h"
#import "NSObject+Swizzling.h"

@implementation UIButton (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodSwizzlingWithOriginalSelector:@selector(sendAction:to:forEvent:) bySwizzledSelector:@selector(sure_SendAction:to:forEvent:)];
    });
}

- (NSTimeInterval)timeInterval{
    return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
- (void)setTimeInterval:(NSTimeInterval)timeInterval{
    objc_setAssociatedObject(self, @selector(timeInterval), @(timeInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}
//當按鈕點擊事件sendAction 時將會執行sure_SendAction
- (void)sure_SendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    if (self.isIgnore) {
        //不須要被hook
        [self sure_SendAction:action to:target forEvent:event];
        return;
    }
    if ([NSStringFromClass(self.class) isEqualToString:@"UIButton"]) {
        self.timeInterval =self.timeInterval == 0 ?defaultInterval:self.timeInterval;
        if (self.isIgnoreEvent){
            return;
        }else if (self.timeInterval > 0){
            [self performSelector:@selector(resetState) withObject:nil afterDelay:self.timeInterval];
        }
    }
    //此處 methodA和methodB方法IMP互換了,實際上執行 sendAction;因此不會死循環
    self.isIgnoreEvent = YES;
    [self sure_SendAction:action to:target forEvent:event];
}
//runtime 動態綁定 屬性
- (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent{
    // 注意BOOL類型 須要用OBJC_ASSOCIATION_RETAIN_NONATOMIC 不要用錯,不然set方法會賦值出錯
    objc_setAssociatedObject(self, @selector(isIgnoreEvent), @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isIgnoreEvent{
    //_cmd == @select(isIgnore); 和set方法裏一致
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setIsIgnore:(BOOL)isIgnore{
    // 注意BOOL類型 須要用OBJC_ASSOCIATION_RETAIN_NONATOMIC 不要用錯,不然set方法會賦值出錯
    objc_setAssociatedObject(self, @selector(isIgnore), @(isIgnore), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isIgnore{
    //_cmd == @select(isIgnore); 和set方法裏一致
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)resetState{
    [self setIsIgnoreEvent:NO];
}
@end

方法一(不推薦)

使用分類+運行時來替換Button的點擊方法,能夠設置一個時間間隔,點擊事後開啓一個計時器,並關閉按鈕的enable屬性,計時完成後再打開enable.至於cell暫時沒有什麼好點子.網絡

優勢:架構

  • 改動比較小

缺點:併發

  • 首先他要啓動很多定時器
  • 若是點擊完成後,快速返回則不能再次點擊!必須等計時器執行完畢

方法二(能解決問題,但不優雅)

通常咱們的網絡請求框架都會封裝兩到三層AFN,經過大量的block進行嵌套來完成一系列的請求工做.因此咱們能夠設置一個全局id變量,用來記錄當前點擊的buttoncell,在最底層的網絡請求開始時將這個按鈕/cell的enable關閉,成功後再次打開.框架

優勢:ide

  • 能解決問題

缺點:測試

  • 記錄cell點擊,改動也不小
  • 併發的問題
  • 項目架構可能也有不適用的地方

方法三(推薦)

咱們能夠控制UINavigationController中的push方法,代碼很簡單,只須要判斷當前的控制器和推入的控制器是不是相同的一個class就行了.但有一個缺點,若原本就想push一個相同的控制器就很尷尬了.代碼以下:atom

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
        //cell由於網絡請求延遲而屢次push同一頁面
        if (![[super topViewController] isKindOfClass:[viewController class]]) {  // 若是和上一個控制器同樣,隔絕此操做
            [super pushViewController:viewController animated:animated];
        }
    }

方法四(強烈推薦)

連接,這位前輩的方式很巧妙,也解決了我上面的缺點.spa

override func performSegueWithIdentifier(identifier: String, sender: AnyObject?) {
if let navigationController = navigationController {
    guard navigationController.topViewController == self else {
        return
    }
}

super.performSegueWithIdentifier(identifier, sender: sender)
}
相關文章
相關標籤/搜索