在日常開發(fā)中經(jīng)常會(huì)碰到一種bug就是因?yàn)橛脩艨焖冱c(diǎn)擊某個(gè)按鈕,導(dǎo)致頁面重復(fù)push或者重復(fù)發(fā)送網(wǎng)絡(luò)請(qǐng)求。這樣的問題既對(duì)用戶體驗(yàn)有影響,而且還會(huì)一定程度上增加服務(wù)器的壓力。
目前,我為了防止按鈕快速點(diǎn)擊主要使用以下兩種辦法
1.在每次點(diǎn)擊時(shí)先取消之前的操作(網(wǎng)上看到的方法)
[cpp]?view plain?copy
-?(void)buttonClicked:(id)sender??
{??
//這里是關(guān)鍵,點(diǎn)擊按鈕后先取消之前的操作,再進(jìn)行需要進(jìn)行的操作??
[[selfclass]?cancelPreviousPerformRequestsWithTarget:self?selector:@selector(buttonClicked:)?object:sender];??
??[self?performSelector:@selector(buttonClicked:?)withObject:sender?afterDelay:0.2f];??
}??
2.點(diǎn)擊后將按鈕置為不可點(diǎn)擊狀態(tài),幾秒后恢復(fù)
[cpp]?view plain?copy
-(void)buttonClicked:(id)sender{??
??self.button.enabled?=?NO;??
[self?performSelector:@selector(changeButtonStatus)?withObject:nil?afterDelay:1.0f];//防止用戶重復(fù)點(diǎn)擊??
}??
-(void)changeButtonStatus{??
??self.button.enabled?=?YES;??
}??
或者使用:
[cpp]?view plain?copy
-?(void)?timeEnough??
{??
UIButton?*btn=(UIButton*)[self.view?viewWithTag:33];??
btn.selected=NO;???
[timer?invalidate];??
timer=nil;???
}??
-?(void)?btnDone:(UIButton*)btn??
?{??
if(btn.selected)?return;??
?btn.selected=YES;??
[self?performSelector:@selector(timeEnough)?withObject:nil?afterDelay:3.0];//使用延時(shí)進(jìn)行限制。??
//to?do?something.??
}??
iOS客戶端經(jīng)常遇到點(diǎn)擊某個(gè)按鈕發(fā)送一個(gè)請(qǐng)求到服務(wù)器,貌似一個(gè)非常簡(jiǎn)單的需求有的時(shí)候其實(shí)并不是那么簡(jiǎn)單,比如網(wǎng)絡(luò)不好的時(shí)候,用戶重復(fù)點(diǎn)擊一個(gè)按鈕會(huì)發(fā)送多次請(qǐng)求,比如在我負(fù)責(zé)的客戶端來說用戶發(fā)帖功能導(dǎo)致的弊端就是,一個(gè)用戶對(duì)一個(gè)帖子回復(fù)了很多條,有的時(shí)候甚至達(dá)到了10多條,如何解決這一的問題呢。方案其實(shí)有很多。
利用MBProgressHud等控件
眾所周知MBProgressHud或者SVProgresHud經(jīng)常被利用在項(xiàng)目中,主要是在網(wǎng)絡(luò)請(qǐng)求發(fā)起到網(wǎng)絡(luò)相應(yīng)收到的這段時(shí)間在客戶端形成一個(gè)遮罩,可以用來阻止用戶點(diǎn)擊UI進(jìn)行操作,防止某些意外的請(qǐng)求產(chǎn)生。
優(yōu)點(diǎn):解決了用戶重復(fù)點(diǎn)擊多次發(fā)送請(qǐng)求的問題,同時(shí)防止了在某些條件不具備的情況進(jìn)行其他操作引發(fā)客戶端出現(xiàn)問題的出現(xiàn)。
缺點(diǎn):有的時(shí)候不人性化,比如用戶進(jìn)入某個(gè)界面就是網(wǎng)速不好,一直請(qǐng)求數(shù)據(jù),等了好長(zhǎng)時(shí)間都沒有結(jié)果,這個(gè)時(shí)候用戶一般都會(huì)下意識(shí)點(diǎn)擊返回按鈕,但是這種情況下,返回按鈕的點(diǎn)擊事件也是不起作用的。
利用運(yùn)行時(shí)設(shè)置相應(yīng)按鈕點(diǎn)擊間隔
1. 對(duì)UIControl進(jìn)行擴(kuò)展
該方案來自http://www.cocoachina.com/ios/20150828/13260.html
@interfaceUIControl(delay)@property(nonatomic,assign)NSTimeIntervaluxy_acceptEventInterval;// 可以用這個(gè)給重復(fù)點(diǎn)擊加間隔@end#import"UIControl+delay.h"#import//增加兩個(gè)屬性staticconstchar*UIControl_acceptEventInterval="UIControl_acceptEventInterval";staticconstchar*UIControl_ignoreEvent="UIControl_ignoreEvent";@implementationUIControl(delay)//時(shí)間間隔- (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方法和自己實(shí)現(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)擊后會(huì)先進(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)志位[selfperformSelector:@selector(setUxy_ignoreEvent:) withObject:@(NO) afterDelay:self.uxy_acceptEventInterval];? ? }//這里其實(shí)是系統(tǒng)的原來的sendAction to方法。[self__uxy_sendAction:action to:target forEvent:event];}@end
2.對(duì)UIButton進(jìn)行擴(kuò)展
該方案來自http://www.tuicool.com/articles/NJvmIf
這個(gè)在點(diǎn)擊UITabbar上的按鈕時(shí)會(huì)崩潰,提示
-[UITabBarButton cs_acceptEventTime]: unrecognized selector sent to instance 0x7fc9d8f36c50,自己找了好久都沒有找到原因,后來參考
http://blog.jobbole.com/79580/改寫了load方法就好了,原因不明白一直不明白,UIButton繼承UIControl應(yīng)該沒有什么問題,為什么UITabbarButton會(huì)出錯(cuò)呢。方案一對(duì)UIControl進(jìn)行擴(kuò)展,在load方法里面直接進(jìn)行了交換,是因?yàn)閁IControl的sendAction:to:event方法確實(shí)是存在的,也許UITabbarButton有特殊的地方吧,就是沒有這個(gè)方法。
@implementationUIButton(delay)// 因category不能添加屬性,只能通過關(guān)聯(lián)對(duì)象的方式。staticconstchar*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);}staticconstchar*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時(shí)執(zhí)行hook+ (void)load {staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{? ? ? ? Classclass= [selfclass];//分別獲取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);//先嘗試給原來的方法添加實(shí)現(xiàn),如果原來的方法不存在就可以添加成功。返回為YES,否則//返回為NO。//UIButton 真的沒有sendAction方法的實(shí)現(xiàn),這是繼承了UIControl的而已,UIControl才真正的實(shí)現(xiàn)了。BOOLdidAddMethod =? ? ? ? class_addMethod(class,? ? ? ? ? ? ? ? ? ? ? ? beforeSelector,? ? ? ? ? ? ? ? ? ? ? ? method_getImplementation(afterMethod),? ? ? ? ? ? ? ? ? ? ? ? method_getTypeEncoding(afterMethod));NSLog(@"%d",didAddMethod);if(didAddMethod) {// 如果之前不存在,但是添加成功了,此時(shí)添加成功的是cs_sendAction方法的實(shí)現(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([NSDatedate].timeIntervalSince1970 -self.cs_acceptEventTime 0) {self.cs_acceptEventTime = [NSDatedate].timeIntervalSince1970;? ? }? ? [selfcs_sendAction:action to:target forEvent:event];? ? }@end
優(yōu)點(diǎn):有效解決了用戶雙擊UI造成事件觸發(fā)兩次的情況(不僅僅局限網(wǎng)絡(luò)請(qǐng)求)/
缺點(diǎn) :在網(wǎng)絡(luò)不好的情況下,很可能在m秒內(nèi)確實(shí)沒有收到服務(wù)器響應(yīng)。如果用戶一直點(diǎn)擊按鈕,很可能觸發(fā)重復(fù)點(diǎn)擊。而且可能和系統(tǒng)以及存在的事件沖突,有的時(shí)候會(huì)產(chǎn)生莫名其妙的錯(cuò)誤。比如我加入這個(gè)類擴(kuò)展后,項(xiàng)目中選擇照片時(shí)候進(jìn)行拍照上傳的時(shí)候,本來需要點(diǎn)擊一下拍攝按鈕就可以成功的事情,確需要長(zhǎng)時(shí)間觸摸才能生效,所以這個(gè)方案待改進(jìn)。
客戶端網(wǎng)絡(luò)請(qǐng)求方法中過濾
一個(gè)網(wǎng)絡(luò)請(qǐng)求包含兩部分:url和參數(shù),因此我們可以在網(wǎng)絡(luò)請(qǐng)求方類里面增加一個(gè)NSMutableArray,用戶url和參數(shù)的md5進(jìn)行一次加密作為key,發(fā)送之前我們可以對(duì)其值賦值為任意固定值,當(dāng)服務(wù)器返回結(jié)果的時(shí)候我們可以將這個(gè)鍵值對(duì)移除。每次發(fā)送網(wǎng)絡(luò)請(qǐng)求前,先從這個(gè)字典中查看本次請(qǐng)求的md5值是否存在,如果存在表明本次請(qǐng)求已經(jīng)發(fā)送但是尚未收到響應(yīng),此時(shí)應(yīng)該return,不再進(jìn)行網(wǎng)絡(luò)請(qǐng)求,否則就是收到響應(yīng)了或者該請(qǐng)求是第一次發(fā)出,改方法貌似不錯(cuò)。
注意:有的時(shí)候參數(shù)包含了時(shí)間戳,這樣計(jì)算永遠(yuǎn)會(huì)不相同的,md5加密之前要清除參數(shù)中的時(shí)間戳或者隨機(jī)字段。
交給服務(wù)器解決
上面的辦法都是客戶端進(jìn)行解決的,其實(shí)仔細(xì)想想這個(gè)問題服務(wù)器端難道就能完全沒有責(zé)任嗎?顯然不是! 比如有人惡意模仿客戶端模擬頻繁向服務(wù)器發(fā)出http請(qǐng)求,這勢(shì)必會(huì)造成服務(wù)器端資源浪費(fèi),雖然說http協(xié)議是不能記住狀態(tài)的(需要靠session技術(shù)實(shí)現(xiàn)),但是服務(wù)器對(duì)這樣的行為就束手無策,顯然是不符合常理的。介于本人對(duì)服務(wù)器的技術(shù)了解有限,所以感覺應(yīng)該上一種解決方案里面的客戶端實(shí)現(xiàn)的過濾加入到服務(wù)器端實(shí)現(xiàn),基本和客戶端一致。
具體方案參考:
服務(wù)器把每次把收到的請(qǐng)求進(jìn)行MD5加密,作為一個(gè)字典的鍵,值可以設(shè)置任意,然后查找數(shù)據(jù)庫,查找回來以后通過適當(dāng)?shù)男问椒祷乜蛻舳耍诓檎覕?shù)據(jù)期間,收到請(qǐng)求先從字典查找鍵是否存在如果已經(jīng)存在就不作出響應(yīng),因?yàn)檎诓檎抑校駝t操作數(shù)據(jù)庫查找數(shù)據(jù),并且將鏈接鍵入到字典里面。
作者:鴻雁長(zhǎng)飛光不度
鏈接:http://www.lxweimin.com/p/a830d0a57378
來源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。