iosswiftthrottling

How can I debounce a method call?


I'm trying to use a UISearchView to query google places. In doing so, on text change calls for my UISearchBar, I'm making a request to google places. The problem is I'd rather debounce this call to only request once per 250 ms in order to avoid unnecessary network traffic. I'd rather not write this functionality myself, but I will if I need to.

I found: https://gist.github.com/ShamylZakariya/54ee03228d955f458389 , but I'm not quite sure how to use it:

func debounce( delay:NSTimeInterval, #queue:dispatch_queue_t, action: (()->()) ) -> ()->() {

    var lastFireTime:dispatch_time_t = 0
    let dispatchDelay = Int64(delay * Double(NSEC_PER_SEC))

    return {
        lastFireTime = dispatch_time(DISPATCH_TIME_NOW,0)
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                dispatchDelay
            ),
            queue) {
                let now = dispatch_time(DISPATCH_TIME_NOW,0)
                let when = dispatch_time(lastFireTime, dispatchDelay)
                if now >= when {
                    action()
                }
            }
    }
}

Here is one thing I've tried using the above code:

let searchDebounceInterval: NSTimeInterval = NSTimeInterval(0.25)

func findPlaces() {
    // ...
}

func searchBar(searchBar: UISearchBar!, textDidChange searchText: String!) {
    debounce(
        searchDebounceInterval,
        dispatch_get_main_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT),
        self.findPlaces
    )
}

The resulting error is Cannot invoke function with an argument list of type '(NSTimeInterval, $T5, () -> ())

How do I use this method, or is there a better way to do this in iOS/Swift.


Solution

  • Put this at the top level of your file so as not to confuse yourself with Swift's funny parameter name rules. Notice that I've deleted the # so that now none of the parameters have names:

    func debounce( delay:NSTimeInterval, queue:dispatch_queue_t, action: (()->()) ) -> ()->() {
        var lastFireTime:dispatch_time_t = 0
        let dispatchDelay = Int64(delay * Double(NSEC_PER_SEC))
    
        return {
            lastFireTime = dispatch_time(DISPATCH_TIME_NOW,0)
            dispatch_after(
                dispatch_time(
                    DISPATCH_TIME_NOW,
                    dispatchDelay
                ),
                queue) {
                    let now = dispatch_time(DISPATCH_TIME_NOW,0)
                    let when = dispatch_time(lastFireTime, dispatchDelay)
                    if now >= when {
                        action()
                    }
            }
        }
    }
    

    Now, in your actual class, your code will look like this:

    let searchDebounceInterval: NSTimeInterval = NSTimeInterval(0.25)
    let q = dispatch_get_main_queue()
    func findPlaces() {
        // ...
    }
    let debouncedFindPlaces = debounce(
            searchDebounceInterval,
            q,
            findPlaces
        )
    

    Now debouncedFindPlaces is a function which you can call, and your findPlaces won't be executed unless delay has passed since the last time you called it.