了解了新的專業(yè)名詞:函數(shù)防抖畦浓、函數(shù)節(jié)流源葫;雖是第一次知道這個詞产还,但平常開發(fā)中其實早已接觸使用過了;
防抖與節(jié)流是處理頻繁事件的技術:用以限制事件頻繁地發(fā)生可能造成的問題屯蹦,如:
- 多次執(zhí)行同一事件维哈,造成事件疊加帶來不必要的影響(iOS典型的例子是點擊按鈕Push到其他vc時會連續(xù)跳轉(zhuǎn)多次)
- 頻繁渲染界面等帶來的性能問題
- 頻繁調(diào)用接口請求數(shù)據(jù)帶來的流量壓力
節(jié)流(throttle)
節(jié)流是指高頻事件觸發(fā),但在n秒內(nèi)只會執(zhí)行一次登澜,所以節(jié)流會稀釋函數(shù)的執(zhí)行頻率阔挠;
應用場景:
頻繁點擊按鈕只響應一次事件
防抖(debounce
防抖是指觸發(fā)高頻事件后n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻事件再次被觸發(fā)脑蠕,則重新計算時間;
應用場景:
輸入框輸入文字時實時搜索功能
節(jié)流和防抖的區(qū)別
簡單來說節(jié)流是控制頻率购撼,防抖是控制次數(shù)
單從字面理解的話跪削,防抖節(jié)流容易搞混且不太好理解;下面引用一張圖說明:
以上圖基礎 舉個例子:
Button點擊事件有可能存在極短時間內(nèi)迂求,頻繁的點擊了多次的情況碾盐;針對這種情況分別做節(jié)流和防抖處理interval設置為1000ms,實際的效果如下:
- 函數(shù)節(jié)流
在1000ms內(nèi)連續(xù)點擊了3次揩局,button響應事件a毫玖、x、y谐腰;最終只會立即執(zhí)行a孕豹,丟棄x涩盾、y十气;如果在m ms內(nèi)連續(xù)點擊了n 次,最終會執(zhí)行時間 m/1000 + 1 個事件春霍;即不管有多少事件interval計算是固定的砸西; - 函數(shù)防抖
在1000ms內(nèi)連續(xù)點擊了2次b、c址儒;最終會已最后一次點擊c為準重新計時芹枷,當時間達到interval才執(zhí)行c;而前面的b會丟棄莲趣;如果在m ms內(nèi)連續(xù)點擊了n 次鸳慈,且每每2個點擊間隔都沒有超過1000ms最終只會在n點擊1000ms后執(zhí)行第n個事件(只有一個事件);即interval計算是動態(tài)的喧伞,每次都會以最后一個重新計時走芋;
iOS實現(xiàn)
知道了思路后,其實不同語言實現(xiàn)起來都不難了潘鲫;iOS這邊可以直接使用dispatch實現(xiàn)(當然也有其他方式)
@implementation MMThrottler
- (instancetype)init {
self = [super init];
if (self) {
_interval = .2;
_queue = dispatch_get_main_queue();
_previousDate = NSDate.distantPast;
_semaphore = dispatch_semaphore_create(1);
}
return self;
}
- (void)execute:(void(^)())action {
__weak typeof(self) selfWeak = self;
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
if (_blockItem) {
// 取消上一次事件
dispatch_block_cancel(_blockItem);
}
_blockItem = dispatch_block_create(DISPATCH_BLOCK_BARRIER, ^{
selfWeak.previousDate = NSDate.date;
action();
});
NSTimeInterval seconds = [NSDate.date timeIntervalSinceDate:_previousDate];
NSTimeInterval delaySeconds = seconds > _interval ? 0 : _interval;
if (seconds > _interval) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC)), _queue, _blockItem);
}
dispatch_semaphore_signal(_semaphore);
}
@end
@implementation MMDebouncer
- (instancetype)init {
self = [super init];
if (self) {
_interval = .5;
_queue = dispatch_get_main_queue();
_semaphore = dispatch_semaphore_create(1);
}
return self;
}
- (void)execute:(void(^)())action {
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
if (_blockItem) {
// 取消上一次事件
dispatch_block_cancel(_blockItem);
}
_blockItem = dispatch_block_create(DISPATCH_BLOCK_BARRIER, ^{
action();
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_interval * NSEC_PER_SEC)), _queue, _blockItem);
dispatch_semaphore_signal(_semaphore);
}
@end