# iOS函数防抖与节流 **Repository Path**: BlogDemo/debouncer_throttle ## Basic Information - **Project Name**: iOS函数防抖与节流 - **Description**: iOS 的节流和防抖 - **Primary Language**: Swift - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-04-10 - **Last Updated**: 2021-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### iOS防抖和节流 > 最近项目中做了输入实时搜索, 测试同学发现当输入速度很快时, 会有偶现的闪退, 断点在 `cellForRow` 里的 `dataSource[indexPath.row]` > 开始还非常疑惑为什么会有这样的闪退, 后来才明白了! 当前一次搜到50条数据时,tableview刷新, 数据渲染到第40行时,第二次的搜索结果已经返回,dataSource 已经替换为20个数据, 所以 `dataSource[40]`就会产生闪退 > 接下来就是思考如果解决这个问题: 思路来自之前写前端时学到的函数的防抖/节流, 就想iOS是否也有这样的功能 ##### 函数防抖 函数防抖是当触发函数时,延迟一定的时间在执行函数,如果这段时间内又一次触发, 就重置这个延迟时间, 看下代码 ```swift class Debouncer { private let label: String private let interval: Int private let queue: DispatchQueue private var workItem: DispatchWorkItem? private var lock: DispatchSemaphoreWrapper /// interval: 单位毫秒 init(label: String = "PZDebouncer", interval: Int = 500) { self.label = label self.interval = interval self.queue = DispatchQueue(label: label) self.lock = DispatchSemaphoreWrapper(value: 1) } func call(_ callback: @escaping (() -> ())) { self.lock.sync { self.workItem?.cancel() self.workItem = DispatchWorkItem { callback() } if let workItem = self.workItem { self.queue.asyncAfter(deadline: .now() + DispatchTimeInterval.milliseconds(interval), execute: workItem) } } } } struct DispatchSemaphoreWrapper { private var lock: DispatchSemaphore init(value: Int) { self.lock = DispatchSemaphore(value: 1) } func sync(execute: () -> ()) { _ = lock.wait(timeout: DispatchTime.distantFuture) lock.signal() execute() } } ``` > DispatchQueue: 借助GCD来实现异步调用 > DispatchWorkItem: 帮助DispatchQueue执行队列任务, 可以直接在闭包中写执行代码,非常方便 > DispatchSemaphore: GCD线程信号量,用来保证线程安全 > 以上几个知识点可以自行去了解, 很简单 > 我们主要看`self.workItem?.cancel()`这句代码, 就是防抖中,如果在延迟时间内再次调用,我们就把上次的任务取消掉,重新计时 ##### 函数节流 函数节流是指当用户在同一时间内多次调用函数时, 让函数以一个固定的时间间隔去执行, 看代码 ```swift class Throttle { private let label: String private let interval: Int private let queue: DispatchQueue private var workItem: DispatchWorkItem? private var lock: DispatchSemaphoreWrapper private var lastTime: Date = Date() /// interval: 单位毫秒 init(label: String = "Debouncer", interval: Int = 500) { self.label = label self.interval = interval self.queue = DispatchQueue(label: label) self.lock = DispatchSemaphoreWrapper(value: 1) } func call(_ callback: @escaping (() -> ())) { self.lock.sync { self.workItem?.cancel() self.workItem = DispatchWorkItem { [weak self] in self?.lastTime = Date() callback() } if let workItem = self.workItem { let new = Date() let delay = new.timeIntervalSince1970 - lastTime.timeIntervalSince1970 > Double(self.interval)/1000 ? DispatchTimeInterval.milliseconds(0) : DispatchTimeInterval.milliseconds(self.interval) self.queue.asyncAfter(deadline: .now() + delay, execute: workItem) } } } } ``` > 主要代码与防抖类似, 我们主要看`lastTime`这个成员变量与`delay = xxx` 这一段, 就是我们与上次执行函数时的时间进行对比如果时间大于执行周期, 就执行函数 ##### 防抖与节流的区别 函数节流不管怎样都会在时间间隔内执行函数 函数防抖如果我们一直在触发函数,理论上函数的执行就会被无限期的延迟