1行代碼快速集成按鈕延時處理(hook實戰)
1. 按鈕延時處理事件有什么應用場景?
-
如果你做的是一個帶有輕微社交功能的APP,這類APP一般都會有類似“收藏”、“點贊”、“喜愛”的功能。
-
這些功能其實載體是一個UIButton,如果你在每次用戶點贊的時候都發請求給服務器,假如有些用戶“手便宜”,在那里重復的點擊,就會造成一個請求還沒回來,有連續發送出去好幾個請求。
-
出現這種情況,第一,可能造成服務器不必要的壓力,這簡直是必然的;第二,由于你不確定請求回調什么時候回來,假如用戶把這個控制器銷毀了,你的應用就可能奔潰。
-
這個場景就可以采用按鈕延時處理事件來輕松應對。
2.實例分析?
像下面的demo里寫的這樣:
收藏這類功能的事件鏈是:用戶點擊-->處理點擊 -->發送請求
- 正常情況,用戶點擊按鈕,響應用戶點擊, 發送請求。
- 當使用延時處理以后(我這里設定延時時長為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的以下方法動態添加屬性。void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, const void *key)
3.3 方法交換是什么?
- 記得我們的每一個OC對象都有一個isa指針嗎?這個isa就是指向創建實例對象的類。
- 對象方法保存到類里面,每個類里面都有一個方法列表。
- 當調用對象方法的時候,系統都會來到這個表里查找對應的方法和實現。
- 所謂的方法交換,也就是hook,就是把兩個方法的實現給交換了。就像下面這張圖一,你調用eat方法的時候,就會去找run方法的實現。
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:(nullable id)target forEvent:(nullable UIEvent )event;</code></pre>
官方的解釋翻譯過來是這樣的:這個方法用以傳遞事件消息,是監聽到事件后最先調用的方法,并且它是隨著事件的重復產生而頻繁調用的。
所以我們要實現攔截事件傳遞,重寫這個方法是最優解。
5.代碼實現?
- 首先為UIControl添加創建分類,并且在.h文件里添加屬性。
#import <UIKit/UIKit.h>
@interface UIControl (JPBtnClickDelay)
/** 延遲時間
/
@property(nonatomic)NSTimeInterval jp_acceptEventInterval;
/* 是否接受延遲 /
@property(nonatomic)BOOL jp_ignoreEvent;
@end</code></pre>
- 接下來來到.m文件
#import "UIControl+JPBtnClickDelay.h"
#import <objc/runtime.h>
@implementation UIControl (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;
[self performSelector:@selector(setJp_ignoreEvent:) withObject:@(NO) afterDelay:self.jp_acceptEventInterval];
}
[self jp_sendAction:action to:target forEvent:event];
}
-(void)setJp_ignoreEvent:(BOOL)jp_ignoreEvent{
objc_setAssociatedObject(self, @"jp_ignoreEvent", @(jp_ignoreEvent), OBJC_ASSOCIATION_ASSIGN);
}
-(BOOL)jp_ignoreEvent{
return [objc_getAssociatedObject(self, @"jp_ignoreEvent") integerValue];
}
-(void)setJp_acceptEventInterval:(NSTimeInterval)jp_acceptEventInterval{
objc_setAssociatedObject(self, @"jp_acceptEventInterval", @(jp_acceptEventInterval), OBJC_ASSOCIATION_ASSIGN);
}
-(NSTimeInterval)jp_acceptEventInterval{
return [objc_getAssociatedObject(self, @"jp_acceptEventInterval") 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</code></pre>
6. 分類的使用?
這里有兩個UIButton的實例對象:
[self.normalBtn addTarget:self action:@selector(normalBtnClick) forControlEvents:UIControlEventTouchUpInside];
[self.delayBtn addTarget:self action:@selector(delayBtnClick) forControlEvents:UIControlEventTouchUpInside];
self.delayBtn.jp_acceptEventInterval = 1.0f;</code></pre>
- normalBtn不需要有延時,就什么也不用管,就和使用系統原生的一樣。
- delayBtn需要延時,給它的jp_acceptEventInterval設定一個延時值,它自動就會生效。
7. Demo下載?
請點擊這里去往Github。
8. One more thing ?
如果您對“hook技術”感興趣,或許可以參見我的另外一篇文章“0行代碼集成非死book和推ter的Modal動畫”。我在這篇文章使用hook成功的做到了0行代碼集成一個功能。
來自:http://www.jianshu.com/p/e791b7927f32