Skip to content

Instantly share code, notes, and snippets.

@mzaks
Last active March 4, 2019 09:47
Show Gist options
  • Save mzaks/c69a33a2dcfb1e950206 to your computer and use it in GitHub Desktop.
Save mzaks/c69a33a2dcfb1e950206 to your computer and use it in GitHub Desktop.
Swift debounce function based GCD
import Foundation
/**
Creates and returns a new debounced version of the passed block which will postpone its execution until after wait seconds have elapsed since the last time it was invoked.
It is like a bouncer at a discotheque. He will act only after you shut up for some time.
This technique is important if you have action wich should fire on update, however the updates are to frequent.
Inspired by debounce function from underscore.js ( http://underscorejs.org/#debounce )
*/
public func dispatch_debounce_block(wait : NSTimeInterval, queue : dispatch_queue_t = dispatch_get_main_queue(), block : dispatch_block_t) -> dispatch_block_t {
var cancelable : dispatch_block_t!
return {
cancelable?()
cancelable = dispatch_after_cancellable(dispatch_time(DISPATCH_TIME_NOW, Int64(wait * Double(NSEC_PER_SEC))), queue, block)
}
}
// Big thanks to Claus Höfele for this function
// https://gist.github.com/choefele/5e5a981ed731472b80d9
func dispatch_after_cancellable(when: dispatch_time_t, queue: dispatch_queue_t, block: dispatch_block_t) -> () -> Void {
var isCancelled = false
dispatch_after(when, queue) {
if !isCancelled {
block()
}
}
return {
isCancelled = true
}
}
import XCTest
class debounceTest: XCTestCase {
func test_dispatch_debounce_block_create_with_no_execute() {
// given
let expectation = expectationWithDescription("expect block to be executed");
var counter = 0
let bouncedBlock = dispatch_debounce_block(0.1){
counter++
}
// when
// no executed
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()){
// fulfill expectation after 0.5 seconds
expectation.fulfill()
}
// then
waitForExpectationsWithTimeout(1) {
error in
XCTAssertEqual(counter, 0, "never executed")
}
}
func test_dispatch_debounce_block_executing_only_once() {
// given
let expectation = expectationWithDescription("expect block to be executed");
var counter = 0
let bouncedBlock = dispatch_debounce_block(0.1){
counter++
}
// when
// execute bounce block 10 time in a row
for _ in 1 ... 10 {
bouncedBlock()
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()){
// fulfill expectation after 0.5 seconds
expectation.fulfill()
}
// then
waitForExpectationsWithTimeout(1) {
error in
// only one execution should be performed
XCTAssert(counter == 1, "called only once")
}
}
func test_dispatch_debounce_block_executing_twice() {
// given
let expectation = expectationWithDescription("expect block to be executed");
var counter = 0
let bouncedBlock = dispatch_debounce_block(0.1){
counter++
}
// when
// execute bounce block 10 time in a row
for _ in 1 ... 10 {
bouncedBlock()
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()){
// execute bounce block after 0.5 seconds
bouncedBlock()
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()){
// fulfill expectation after 2 seconds
expectation.fulfill()
}
// then
waitForExpectationsWithTimeout(3) {
error in
XCTAssertEqual(counter, 2, "called twice")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment