iOS - runtime實(shí)現(xiàn)“防止button被重復(fù)點(diǎn)擊”的背后

好吧,今天中午剛吃完飯坚嗜,屁股還沒坐穩(wěn)呢,CTO就跑過來問我诗充,小姑涼啊苍蔬、掃碼購這塊有bug啊,如果我狂點(diǎn)蝴蜓、碟绑、、茎匠、格仲、、去結(jié)算诵冒,會(huì)給我生成尼瑪一堆訂單凯肋,而且支付寶無限重復(fù)喚起啊。汽馋。侮东。我內(nèi)心一開始是拒絕的圈盔,這尼瑪CTO估計(jì)是閑的,后來想想悄雅,這種閑的沒事干的用戶估計(jì)還挺多驱敲,然后聯(lián)想到這將近5、60個(gè)界面啊宽闲,我該咋辦众眨。后來網(wǎng)上一找,果然一大推方案啊容诬,其中最一勞永逸的方案即是利用我們偉大的runtime機(jī)制去實(shí)現(xiàn)一勞永逸的做法娩梨。我內(nèi)心一陣狂喜,按照方案做了一遍放案,果然姚建,大功告成,啥吱殉?你們要我貼方案暗г!其實(shí)網(wǎng)上一大堆的友雳,不過既然你們想要稿湿,那我就貼出來吧。多個(gè)入口押赊,省點(diǎn)時(shí)間饺藤,本著造福跟我一樣萌萌的程序媛,我就犧牲一下自己把流礁。

step1:創(chuàng)建個(gè)UIControl的分類

step2:利用runtime動(dòng)態(tài)的給分類綁定屬性(不會(huì)涕俗?不著急,一會(huì)就給出)

step3:關(guān)鍵:利用method_exchangeImplementations方法交換函數(shù)的實(shí)現(xiàn)(只能交換一次哦)神帅。

代碼如下:

#import "UIControl+HQStopMultiTap.h"

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

//runtime動(dòng)態(tài)綁定屬性
- (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent{
    
    objc_setAssociatedObject(self,@selector(isIgnoreEvent),@(isIgnoreEvent),OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isIgnoreEvent{
    
    return[objc_getAssociatedObject(self,_cmd)boolValue];
    
}
- (void)resetState{
    
    [self setIsIgnoreEvent:NO];
}
+ (void)load{
    
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        
        SEL selA =@selector(sendAction:to:forEvent:);
        
        SEL selB =@selector(mySendAction:to:forEvent:);
        
        Method methodA =class_getInstanceMethod(self, selA);
        
        Method methodB =class_getInstanceMethod(self, selB);
        
        //將methodB的實(shí)現(xiàn)添加到系統(tǒng)方法中也就是說將methodA方法指針添加成方法methodB的再姑。返回值表示是否添加成功
        
        BOOL isAdd =class_addMethod(self, selA,method_getImplementation(methodB),method_getTypeEncoding(methodB));
        //添加成功了說明本類中不存在methodB所以此時(shí)必須將方法b的實(shí)現(xiàn)指針換成方法A的,否則b方法將沒有實(shí)現(xiàn)找御。
        
        if(isAdd) {
            
            class_replaceMethod(self, selB,method_getImplementation(methodA),method_getTypeEncoding(methodA));
            
        }else{
            
            //添加失敗了說明本類中有methodB的實(shí)現(xiàn)元镀,此時(shí)只需要將methodA和methodB的IMP互換一下即可。
            method_exchangeImplementations(methodA, methodB);
        }
    });
}
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event
{
    
    if([NSStringFromClass(self.class)isEqualToString:@"UIButton"]) {
        
        self.timeInterval =self.timeInterval==0 ? 1.5:self.timeInterval;
        
        if(self.isIgnoreEvent){
            
            return;
            
        }else if(self.timeInterval>0){
            
            [self performSelector:@selector(resetState) withObject:nil afterDelay:self.timeInterval];
        }
    }
    //此處methodA和methodB方法IMP互換了霎桅,實(shí)際上執(zhí)行sendAction栖疑;所以不會(huì)死循環(huán)
    self.isIgnoreEvent=YES;
    [self mySendAction:action to:target forEvent:event];
}
@end

分類代碼就貼到這里了√鲜唬可是遇革,小姐姐是這種只想修復(fù)bug的人么?當(dāng)然得知道他背后的故事,對不對澳淑。
首先這個(gè)思想絕對不是我想出來的比原,我只是看完別人貼的代碼以后,做了點(diǎn)自己的思考杠巡,歡迎各位小哥哥們來噴量窘,不過,請手下留情氢拥。
首先runtime動(dòng)態(tài)綁定屬性也沒什么好分析的蚌铜。主要也就是 objc_setAssociatedObject/objc_getAssociatedObject這兩個(gè)方法。
那這兩個(gè)屬性
timeInterval 代表多長時(shí)間內(nèi)不能被連續(xù)點(diǎn)擊
isIgnoreEvent代表某個(gè)button是否不需要這種機(jī)制嫩海,可以自己設(shè)置冬殃。
其實(shí)主要的思想還是交換方法的實(shí)現(xiàn)。
對于一個(gè)給定的事件叁怪,UIControl會(huì)調(diào)用sendAction:to:forEvent:方法审葬,那我們就可以寫一個(gè)自己的方法,讓用戶點(diǎn)擊button的時(shí)候其實(shí)執(zhí)行的是我們自己的方法奕谭,在我們自己的方法里面再去調(diào)用自己的方法(因?yàn)榉椒ㄕ{(diào)換了等同于去調(diào)用了系統(tǒng)的方法)涣觉,但是確保用戶下次點(diǎn)擊的時(shí)候不會(huì)再進(jìn)行調(diào)換,那尼瑪就瞎了血柳,又換回去了官册,搞毛啊。所以难捌,用
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

}
保證只會(huì)交換一次膝宁。OK?
哎根吁,最近小姐姐我也在研究runtime员淫,好高深的樣子,歡迎各位小哥哥來指教击敌。對了介返,還有什么問題也可以給我留言,我盡量正經(jīng)的回答你愚争。。挤聘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轰枝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子组去,更是在濱河造成了極大的恐慌鞍陨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诚撵,居然都是意外死亡缭裆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門寿烟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澈驼,“玉大人,你說我怎么就攤上這事筛武》炱洌” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵徘六,是天一觀的道長内边。 經(jīng)常有香客問我,道長待锈,這世上最難降的妖魔是什么漠其? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮竿音,結(jié)果婚禮上和屎,老公的妹妹穿的比我還像新娘。我一直安慰自己谍失,他們只是感情好眶俩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著快鱼,像睡著了一般颠印。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抹竹,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天线罕,我揣著相機(jī)與錄音,去河邊找鬼窃判。 笑死钞楼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袄琳。 我是一名探鬼主播卖擅,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼劲阎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恐锣,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤糕非,失蹤者是張志新(化名)和其女友劉穎立宜,沒想到半個(gè)月后藕各,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年红伦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了英古。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昙读,死狀恐怖召调,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情箕戳,我是刑警寧澤某残,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站陵吸,受9級(jí)特大地震影響玻墅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壮虫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一澳厢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧囚似,春花似錦剩拢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至募狂,卻和暖如春办素,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祸穷。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工性穿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雷滚。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓需曾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親祈远。 傳聞我的和親對象是個(gè)殘疾皇子呆万,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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