iOS客戶端防止發(fā)送重復(fù)點(diǎn)擊發(fā)請求

iOS客戶端經(jīng)常遇到點(diǎn)擊某個按鈕發(fā)送一個請求到服務(wù)器热凹,貌似一個非常簡單的需求有的時候其實并不是那么簡單,比如網(wǎng)絡(luò)不好的時候颇玷,用戶重復(fù)點(diǎn)擊一個按鈕會發(fā)送多次請求陵究,比如在我負(fù)責(zé)的客戶端來說用戶發(fā)帖功能導(dǎo)致的弊端就是辉词,一個用戶對一個帖子回復(fù)了很多條禽绪,有的時候甚至達(dá)到了10多條医瘫,如何解決這一的問題呢侣肄。方案其實有很多。

利用MBProgressHud等控件

眾所周知MBProgressHud或者SVProgresHud經(jīng)常被利用在項目中醇份,主要是在網(wǎng)絡(luò)請求發(fā)起到網(wǎng)絡(luò)相應(yīng)收到的這段時間在客戶端形成一個遮罩稼锅,可以用來阻止用戶點(diǎn)擊UI進(jìn)行操作,防止某些意外的請求產(chǎn)生僚纷。

  • 優(yōu)點(diǎn):解決了用戶重復(fù)點(diǎn)擊多次發(fā)送請求的問題矩距,同時防止了在某些條件不具備的情況進(jìn)行其他操作引發(fā)客戶端出現(xiàn)問題的出現(xiàn)。
  • 缺點(diǎn):有的時候不人性化怖竭,比如用戶進(jìn)入某個界面就是網(wǎng)速不好锥债,一直請求數(shù)據(jù),等了好長時間都沒有結(jié)果,這個時候用戶一般都會下意識點(diǎn)擊返回按鈕哮肚,但是這種情況下登夫,返回按鈕的點(diǎn)擊事件也是不起作用的。

利用運(yùn)行時設(shè)置相應(yīng)按鈕點(diǎn)擊間隔

1. 對UIControl進(jìn)行擴(kuò)展

該方案來自http://www.cocoachina.com/ios/20150828/13260.html

@interface UIControl (delay)
@property (nonatomic, assign) NSTimeInterval uxy_acceptEventInterval;   // 可以用這個給重復(fù)點(diǎn)擊加間隔
@end
#import "UIControl+delay.h"
#import <objc/runtime.h>

//增加兩個屬性
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";
static const char *UIControl_ignoreEvent = "UIControl_ignoreEvent";

@implementation UIControl (delay)
//時間間隔
- (NSTimeInterval)uxy_acceptEventInterval
{
    return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}
- (void)setUxy_acceptEventInterval:(NSTimeInterval)uxy_acceptEventInterval
{
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(uxy_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//是否響應(yīng)事件的標(biāo)志位
-(BOOL)uxy_ignoreEvent
{
    return [objc_getAssociatedObject(self, UIControl_ignoreEvent) boolValue];
}
-(void)setUxy_ignoreEvent:(BOOL)uxy_ignoreEvent
{
    objc_setAssociatedObject(self, UIControl_ignoreEvent, @(uxy_ignoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+(void)load
{
    //將系統(tǒng)的sendAction方法和自己實現(xiàn)的方法進(jìn)行互換
    Method a=class_getInstanceMethod(self,@selector(sendAction:to:forEvent:));
    Method b = class_getInstanceMethod(self,@selector(__uxy_sendAction:to:forEvent:));
    method_exchangeImplementations(a,b);
}
//點(diǎn)擊后會先進(jìn)入這里
- (void)__uxy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    if (self.uxy_ignoreEvent)//根據(jù)狀態(tài)判斷是否繼續(xù)執(zhí)行
        return;
    if (self.uxy_acceptEventInterval > 0)
    {
        self.uxy_ignoreEvent = YES;
        //周期性清空標(biāo)志位
        [self performSelector:@selector(setUxy_ignoreEvent:) withObject:@(NO) afterDelay:self.uxy_acceptEventInterval];
    }
    //這里其實是系統(tǒng)的原來的sendAction to方法允趟。
    [self __uxy_sendAction:action to:target forEvent:event];
}

@end

2.對UIButton進(jìn)行擴(kuò)展

該方案來自 http://www.tuicool.com/articles/NJvmIf

這個在點(diǎn)擊UITabbar上的按鈕時會崩潰恼策,提示
-[UITabBarButton cs_acceptEventTime]: unrecognized selector sent to instance 0x7fc9d8f36c50,自己找了好久都沒有找到原因潮剪,后來參考
http://blog.jobbole.com/79580/ 改寫了load方法就好了涣楷,原因不明白一直不明白,UIButton繼承UIControl應(yīng)該沒有什么問題抗碰,為什么UITabbarButton會出錯呢总棵。方案一對UIControl進(jìn)行擴(kuò)展,在load方法里面直接進(jìn)行了交換改含,是因為UIControl的sendAction:to:event方法確實是存在的,也許UITabbarButton有特殊的地方吧,就是沒有這個方法迄汛。

@implementation UIButton (delay)

// 因category不能添加屬性捍壤,只能通過關(guān)聯(lián)對象的方式。
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時執(zhí)行hook
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        //分別獲取
        SEL beforeSelector = @selector(sendAction:to:forEvent:);
        SEL afterSelector = @selector(cs_sendAction:to:forEvent:);
        
        Method beforeMethod = class_getInstanceMethod(class, beforeSelector);
        Method afterMethod = class_getInstanceMethod(class, afterSelector);
        //先嘗試給原來的方法添加實現(xiàn)鞍爱,如果原來的方法不存在就可以添加成功鹃觉。返回為YES,否則
        //返回為NO睹逃。
        //UIButton 真的沒有sendAction方法的實現(xiàn)盗扇,這是繼承了UIControl的而已,UIControl才真正的實現(xiàn)了沉填。
        BOOL didAddMethod =
        class_addMethod(class,
                        beforeSelector,
                        method_getImplementation(afterMethod),
                        method_getTypeEncoding(afterMethod));
        NSLog(@"%d",didAddMethod);
        if (didAddMethod) {
            // 如果之前不存在疗隶,但是添加成功了,此時添加成功的是cs_sendAction方法的實現(xiàn)
            // 這里只需要方法替換
            class_replaceMethod(class,
                                afterSelector,
                                method_getImplementation(beforeMethod),
                                method_getTypeEncoding(beforeMethod));
        } else {
            //本來如果存在就進(jìn)行交換
            method_exchangeImplementations(afterMethod, beforeMethod);
        }
    });
}
- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([NSDate date].timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_acceptEventInterval) {
        return;
    }
    if (self.cs_acceptEventInterval > 0) {
        self.cs_acceptEventTime = [NSDate date].timeIntervalSince1970;
    }
    [self cs_sendAction:action to:target forEvent:event];
    
}
@end

  • 優(yōu)點(diǎn):有效解決了用戶雙擊UI造成事件觸發(fā)兩次的情況(不僅僅局限網(wǎng)絡(luò)請求)/
  • 缺點(diǎn) :在網(wǎng)絡(luò)不好的情況下翼闹,很可能在m秒內(nèi)確實沒有收到服務(wù)器響應(yīng)斑鼻。如果用戶一直點(diǎn)擊按鈕,很可能觸發(fā)重復(fù)點(diǎn)擊猎荠。而且可能和系統(tǒng)以及存在的事件沖突坚弱,有的時候會產(chǎn)生莫名其妙的錯誤。比如我加入這個類擴(kuò)展后关摇,項目中選擇照片時候進(jìn)行拍照上傳的時候荒叶,本來需要點(diǎn)擊一下拍攝按鈕就可以成功的事情,確需要長時間觸摸才能生效,所以這個方案待改進(jìn)输虱。

客戶端網(wǎng)絡(luò)請求方法中過濾

一個網(wǎng)絡(luò)請求包含兩部分:url參數(shù),因此我們可以在網(wǎng)絡(luò)請求方類里面增加一個NSMutableArray些楣,用戶url參數(shù)的md5進(jìn)行一次加密作為key,發(fā)送之前我們可以對其值賦值為任意固定值,當(dāng)服務(wù)器返回結(jié)果的時候我們可以將這個鍵值對移除戈毒。每次發(fā)送網(wǎng)絡(luò)請求前艰猬,先從這個字典中查看本次請求的md5值是否存在,如果存在表明本次請求已經(jīng)發(fā)送但是尚未收到響應(yīng)埋市,此時應(yīng)該return冠桃,不再進(jìn)行網(wǎng)絡(luò)請求,否則就是收到響應(yīng)了或者該請求是第一次發(fā)出道宅,改方法貌似不錯食听。

注意:有的時候參數(shù)包含了時間戳,這樣計算永遠(yuǎn)會不相同的污茵,md5加密之前要清除參數(shù)中的時間戳或者隨機(jī)字段樱报。


交給服務(wù)器解決

上面的辦法都是客戶端進(jìn)行解決的,其實仔細(xì)想想這個問題服務(wù)器端難道就能完全沒有責(zé)任嗎?顯然不是! 比如有人惡意模仿客戶端模擬頻繁向服務(wù)器發(fā)出http請求泞当,這勢必會造成服務(wù)器端資源浪費(fèi)迹蛤,雖然說http協(xié)議是不能記住狀態(tài)的(需要靠session技術(shù)實現(xiàn)),但是服務(wù)器對這樣的行為就束手無策襟士,顯然是不符合常理的盗飒。介于本人對服務(wù)器的技術(shù)了解有限,所以感覺應(yīng)該上一種解決方案里面的客戶端實現(xiàn)的過濾加入到服務(wù)器端實現(xiàn)陋桂,基本和客戶端一致逆趣。

具體方案參考:


服務(wù)器把每次把收到的請求進(jìn)行MD5加密,作為一個字典的鍵嗜历,值可以設(shè)置任意宣渗,然后查找數(shù)據(jù)庫,查找回來以后通過適當(dāng)?shù)男问椒祷乜蛻舳死嬷荩诓檎覕?shù)據(jù)期間痕囱,收到請求先從字典查找鍵是否存在如果已經(jīng)存在就不作出響應(yīng),因為正在查找中摊唇,否則操作數(shù)據(jù)庫查找數(shù)據(jù)咐蝇,并且將鏈接鍵入到字典里面。

上述方案是本人工作中的思考還有互聯(lián)網(wǎng)上查找的方案總結(jié)巷查,難免有不足之處有序,僅供參考。希望各位能夠提供更好的解決方案岛请,歡迎留言旭寿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市崇败,隨后出現(xiàn)的幾起案子盅称,更是在濱河造成了極大的恐慌肩祥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缩膝,死亡現(xiàn)場離奇詭異混狠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疾层,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門将饺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痛黎,你說我怎么就攤上這事予弧。” “怎么了湖饱?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵掖蛤,是天一觀的道長。 經(jīng)常有香客問我井厌,道長蚓庭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任仅仆,我火速辦了婚禮彪置,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝇恶。我一直安慰自己,他們只是感情好惶桐,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布撮弧。 她就那樣靜靜地躺著,像睡著了一般姚糊。 火紅的嫁衣襯著肌膚如雪贿衍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天救恨,我揣著相機(jī)與錄音贸辈,去河邊找鬼。 笑死肠槽,一個胖子當(dāng)著我的面吹牛擎淤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秸仙,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼嘴拢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寂纪?” 一聲冷哼從身側(cè)響起席吴,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赌结,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后孝冒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柬姚,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年庄涡,在試婚紗的時候發(fā)現(xiàn)自己被綠了量承。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡啼染,死狀恐怖宴合,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迹鹅,我是刑警寧澤卦洽,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站斜棚,受9級特大地震影響阀蒂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弟蚀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一蚤霞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧义钉,春花似錦昧绣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至删壮,卻和暖如春贪绘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背央碟。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工税灌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亿虽。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓菱涤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親洛勉。 傳聞我的和親對象是個殘疾皇子狸窘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)坯认,斷路器翻擒,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,510評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫氓涣、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,024評論 4 62
  • 那么好的天氣陋气,卻是我負(fù)能量爆棚的一天劳吠!我想發(fā)泄,卻只能憋著巩趁,估計得癌癥的人都是這么郁悶死的吧
    _子曰_閱讀 120評論 0 0
  • 姓名:魏正君《六項精進(jìn)》第270期感謝2組 公司:綿陽大北農(nóng)農(nóng)牧科技有限公司 【日精進(jìn)打卡第10天】 【知~學(xué)習(xí)】...
    莫心莫肺閱讀 146評論 0 0