1. 按鈕延時處理事件有什么應用場景?
如果你做的是一個帶有輕微社交功能的APP,這類APP一般都會有類似“收藏”、“點贊”、“喜愛”的功能。
這些功能其實載體是一個UIButton,如果你在每次用戶點贊的時候都發請求給服務器,假如有些用戶“手便宜”,在那里重復的點擊,就會造成一個請求還沒回來,有連續發送出去好幾個請求。
出現這種情況,第一,可能造成服務器不必要的壓力,這簡直是必然的;第二,由于你不確定請求回調什么時候回來,假如用戶把這個控制器銷毀了,你的應用就可能奔潰。
這個場景就可以采用按鈕延時處理事件來輕松應對。
2.實例分析?
像下面的demo里寫的這樣:
JPBtnClickDelay
收藏這類功能的事件鏈是:用戶點擊-->處理點擊 -->發送請求
正常情況,用戶點擊按鈕,響應用戶點擊, 發送請求。
當使用延時處理以后(我這里設定延時時長為1.0Second),當用戶點擊按鈕以后,響應用戶點擊,但是不是立即發送請求,而是先檢查一下兩次點擊之間時間差有沒有1秒,如果有,再發送請求,如果沒有,不發送請求。
3、動態添加方法和屬性(hook)?
3.1 runtime是什么?
runtime簡稱運行時。OC就是運行時機制,也就是在運行時候的一些機制,其中最主要的是消息機制。
Objective-C 的 Runtime 是一個運行時庫(Runtime Library),它是一個主要使用 C 和匯編寫的庫,為 C 添加了面相對象的能力并創造了 Objective-C。這就是說它在類信息(Class information) 中被加載,完成所有的方法分發,方法轉發,等等。Objective-C runtime 創建了所有需要的結構體,讓 Objective-C 的面相對象編程變為可能。
3.2 動態添加方法和屬性是什么?
比如說,我要給一個人動態添加一個“吹牛逼”的屬性,方法是這樣的。先給人添加一個分類(Category),然后在分類里添加一個屬性。
注意,分類是專門用來添加方法的,在分類里使用關鍵字@property添加屬性,系統是不會幫我們生成setter-getter方法的。
所以我們要自己實現setter-getter方法。
在setter方法里使用runtime的以下方法動態添加屬性。
voidobjc_setAssociatedObject(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy)
在getter方法里使用runtime的以下方法動態獲取屬性值。
idobjc_getAssociatedObject(idobject,constvoid*key)
3.3 方法交換是什么?
記得我們的每一個OC對象都有一個isa指針嗎?這個isa就是指向創建實例對象的類。
對象方法保存到類里面,每個類里面都有一個方法列表。
當調用對象方法的時候,系統都會來到這個表里查找對應的方法和實現。
方法映射表.png
所謂的方法交換,也就是hook,就是把兩個方法的實現給交換了。就像下面這張圖一,你調用eat方法的時候,就會去找run方法的實現。
hook.png
4.思路分析?
我們知道UIButton繼承自UIControl,UIButton的所有處理事件的能力都是它的父類UIControl傳給它的。UIControl有這樣一個方法:
// send the action. the first method is called for the event and is a point at which you can observe or override behavior. it is called repeately by the second.- (void)sendAction:(SEL)action to:(nullableid)target forEvent:(nullableUIEvent*)event;
官方的解釋翻譯過來是這樣的:這個方法用以傳遞事件消息,是監聽到事件后最先調用的方法,并且它是隨著事件的重復產生而頻繁調用的。
所以我們要實現攔截事件傳遞,重寫這個方法是最優解。
5.代碼實現?
首先為UIControl添加創建分類,并且在.h文件里添加屬性。
#import@interfaceUIControl(JPBtnClickDelay)/** 延遲時間 */@property(nonatomic)NSTimeIntervaljp_acceptEventInterval;@end
接下來來到.m文件
#import"UIControl+JPBtnClickDelay.h"#import@interfaceUIControl()/** 是否忽略點擊 */@property(nonatomic)BOOLjp_ignoreEvent;@end@implementationUIControl(JPBtnClickDelay)-(void)jp_sendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event{if(self.jp_ignoreEvent)return;if(self.jp_acceptEventInterval>0) {self.jp_ignoreEvent=YES;? ? [selfperformSelector:@selector(setJp_ignoreEvent:) withObject:@(NO) afterDelay:self.jp_acceptEventInterval];}? ? [selfjp_sendAction:action to:target forEvent:event];}-(void)setJp_ignoreEvent:(BOOL)jp_ignoreEvent{? ? objc_setAssociatedObject(self,@selector(jp_ignoreEvent), @(jp_ignoreEvent), OBJC_ASSOCIATION_ASSIGN);}-(BOOL)jp_ignoreEvent{return[objc_getAssociatedObject(self, _cmd, boolValue];}-(void)setJp_acceptEventInterval:(NSTimeInterval)jp_acceptEventInterval{? ? objc_setAssociatedObject(self,@selector(jp_acceptEventInterval), @(jp_acceptEventInterval), OBJC_ASSOCIATION_ASSIGN);}-(NSTimeInterval)jp_acceptEventInterval{return[objc_getAssociatedObject(self, _cmd) doubleValue];}+(void)load{? ? Method sys_Method = class_getInstanceMethod(self,@selector(sendAction:to:forEvent:));? ? Method add_Method = class_getInstanceMethod(self,@selector(jp_sendAction:to:forEvent:));? ? method_exchangeImplementations(sys_Method, add_Method);}@end
6. 分類的使用?
這里有兩個UIButton的實例對象:
[self.normalBtnaddTarget:selfaction:@selector(normalBtnClick) forControlEvents:UIControlEventTouchUpInside];[self.delayBtnaddTarget:selfaction:@selector(delayBtnClick) forControlEvents:UIControlEventTouchUpInside];self.delayBtn.jp_acceptEventInterval=1.0f;
normalBtn不需要有延時,就什么也不用管,就和使用系統原生的一樣。
delayBtn需要延時,給它的jp_acceptEventInterval設定一個延時值,它自動就會生效。
7. Demo下載?
請點擊這里去往Github。