前言
函数防抖与节流不是新概念,在前端领域很常见,也是面试中的常客,搜索”前端 函数防抖”能看到很多文章。
相反,在 iOS 上却看不到很多介绍。
第一次知道 函数防抖与节流
,是在 2018 年做交易所项目:
当时的场景,是实时更新交易数据。
交易订单数据变化很频繁,每次都去刷新,显然不是一个好方法。
而且不能直接丢数据,常规的”第一次执行,后续丢弃”的限频策略,满足不了需求。
当时思考,这个策略应满足的条件:
- 一定时间内,合并多次触发为一次,并且触发时数据是最新的.
因为代码实现问题,和大佬请教。说明完目的,他一听就说,这不是函数防抖和节流吗?在前端很常见..
好嘛…原来人家前端早就有了?我又学会了新姿势。
而我发现这个概念,不仅是前端,后端也能应用。甚至 TCP 的流量控制策略,就是属于函数防抖。
什么是函数防抖和节流
前面解释了为什么
要用到 函数防抖和节流
,现在说说它们具体是什么。
很多文章都提到一个演示的网址 debounce&throttle,里面模拟鼠标移动事件几种情况的调用,带颜色的竖线,代表一次函数执行。
使用的大概效果是这样:
regular
代表常规情况,不做限制时,函数直接调用的结果。deboundce
代表防抖,可以发现,如果函数一直调用,它不会立即执行,而是等到一段时间后,函数没有新调用,它才执行一次。throttle
代表节流,在一定时间内,只执行一次
👾 防抖 (Debounce)
防抖的情况,有点像一个极度珍惜
执行机会
的人,只要时间段内,有任务来,就再等一会。等到最后一次,超过一定时间,确定没有新任务了,才去做执行。
有人觉得它像黑车司机,有人形容它是上班时的电梯,但黑车或者电梯容量满了都会开走。
而我认为,它就像一只耐心上好的怪兽,等到所有食物都来完了,确定没有新食物,再张开它的大嘴,一网打尽。
🐯 节流 (Throttle)
节流比较好理解,在一定时间段内,丢弃掉其它触发,就做一次执行。
使用场景
函数节流
的使用场景:
防止多次点击
重复发多个网络网络请求
等等..
其实函数节流
最简单的实现方式,仅用时间戳对比,就可以办到,大家一般这么写:
1 | if((now-last)<time){ |
很多人已经用过了,只是不知道名称。
而特殊一点的节流需求:
时间段内,只执行最后一次触发,丢掉之前的触发。
碰到的应用场景是,消息队列在时间段内有数据变化,在最后一次进行批量处理传递。
函数防抖
,我看到的使用场景:
- 列表刷新,为避免短时间内反复 reload,可以多次合并为一次
- TCP 流量控制
- 直播房间的全屏游戏界面,点击 1 次出现控制工具,一定时间内,多次点击不隐藏工具。等时间过去后,执行自动隐藏
现成的轮子 - MessageThrottle
按照套路,该亮出自己的代码来实现了。
然而 iOS 也早有人实现了轮子,不重复造轮子嘛,可以直接使用。
发现 MessageThrottle 是比较完备的实现,而且在手 Q 中应用了,质量比较可靠。推荐一下。
MessageThrottle 使用
它的使用很简单:
1 | Stub *s = [Stub new]; |
主要就是对 MTRule
的设置,决定我们将以哪种模式,多少的时间限制来控制方法调用。
MessageThrottle 分析
虽说不再造轮子,但要了解它是什么样的。当然如不感兴趣,看使用也够了。
整个库就只有 MessageThrottle.h
和 MessageThrottle.m
两个文件。
主要思路是:对进行节流和防抖的方法,进行 hook,然后再统一做处理。
其实里面能学习的点不少,这里只大概介绍一下。
主要设计思路
引用作者自己说明主要类关系的图,虚线代表弱引用:
NSMapTable 存储数据
MTEngine 中通过 NSMapTable
来以target
作为key,selector
数组作为 value,来存储管理数据。
NSMapTable
的一个特性是支持任意指针作为 Key 且无需持有,NSMapTable
也会自动移除那些键或值为 nil
的数据。
通过关联对象进行规则移除
一个关键设计点在于,使用关联对象,将 MTDealloc
对象关联在 target
上:
1 | - (MTDealloc *)mt_deallocObject |
关联对象设计的好处是:
在 target 释放时,关联对象也是会被清除的,所以 MTDealloc 对象也会释放,达到了 target 释放时自动移除 rule 的效果。
在 MTDealloc 的 dealloc 方法进行discard
操作:
1 | - (void)dealloc |
里面调用写的有点骚…其实就是:
1 | [MTEngine.defaultEngine discardRule:self.rule whenTargetDealloc:self]; |
消息转发中的核心处理逻辑
整个库的核心处理在 mt_handleInvocation
中:
1 | /** |
而作者自己也写了相关 4 篇相关的说明:
- Objective-C Message Throttle and Debounce
- Associated Object 与 Dealloc
- MessageThrottle Performance Benchmark and Optimization
- MessageThrottle Safety
限于篇幅,不再继续放代码了,可以详细阅读作者说明和源码。
这次感想就是:
- 再次比较的深体会到,很多概念或者策略,整个大前端领域是基本通用的,甚至在整个计算机技术里都是通用的。
- 每当自己有什么想法,基本上前人都会有很好的实现了,大家常常是站在巨人的肩膀上做事情。感谢这么多优秀程序员的创造与分享