如何去除方法调用?

Par*_*ris 22 throttling ios swift

我正在尝试用a UISearchView来查询谷歌的地方.在这样做的时候,对于我的文本更改调用UISearchBar,我正在向google地方发出请求.问题是我宁愿将此调用去抖动,每250毫秒只请求一次,以避免不必要的网络流量.我不想自己写这个功能,但如果需要,我会.

我发现:https://gist.github.com/ShamylZakariya/54ee03228d955f458389,但我不太确定如何使用它:

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()
                }
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是我尝试使用上述代码的一件事:

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
    )
}
Run Code Online (Sandbox Code Playgroud)

产生的错误是 Cannot invoke function with an argument list of type '(NSTimeInterval, $T5, () -> ())

我如何使用此方法,或者有更好的方法在iOS/Swift中执行此操作.

Khr*_*rob 28

对于那些不想创建类/扩展的人来说,这是一个选项:

在您的代码中的某处:

var debounce_timer:Timer?
Run Code Online (Sandbox Code Playgroud)

在你想要做去抖的地方:

debounce_timer?.invalidate()
debounce_timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in 
    print ("Debounce this...") 
}
Run Code Online (Sandbox Code Playgroud)

  • 本页中最简单的解决方案,为什么比这更复杂呢? (2认同)
  • 这是正确的答案。如果人们为一个非常简单的问题提供两页纸的答案,你应该始终保持怀疑。 (2认同)

mat*_*att 16

将它放在文件的顶层,以免混淆Swift的有趣参数名称规则.请注意,我已删除了#所有参数,因此现在没有任何参数具有名称:

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()
                }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,在您的实际课程中,您的代码将如下所示:

let searchDebounceInterval: NSTimeInterval = NSTimeInterval(0.25)
let q = dispatch_get_main_queue()
func findPlaces() {
    // ...
}
let debouncedFindPlaces = debounce(
        searchDebounceInterval,
        q,
        findPlaces
    )
Run Code Online (Sandbox Code Playgroud)

现在debouncedFindPlaces是一个你可以调用的函数,findPlaces除非delay你上次调用它,否则你将不会被执行.


d4R*_*4Rk 16

Swift 3版

1.基本去抖功能

func debounce(interval: Int, queue: DispatchQueue, action: @escaping (() -> Void)) -> () -> Void {
    var lastFireTime = DispatchTime.now()
    let dispatchDelay = DispatchTimeInterval.milliseconds(interval)

    return {
        lastFireTime = DispatchTime.now()
        let dispatchTime: DispatchTime = DispatchTime.now() + dispatchDelay

        queue.asyncAfter(deadline: dispatchTime) {
            let when: DispatchTime = lastFireTime + dispatchDelay
            let now = DispatchTime.now()
            if now.rawValue >= when.rawValue {
                action()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

2.参数化去抖功能

有时让debounce函数采用参数是有用的.

typealias Debounce<T> = (_ : T) -> Void

func debounce<T>(interval: Int, queue: DispatchQueue, action: @escaping Debounce<T>) -> Debounce<T> {
    var lastFireTime = DispatchTime.now()
    let dispatchDelay = DispatchTimeInterval.milliseconds(interval)

    return { param in
        lastFireTime = DispatchTime.now()
        let dispatchTime: DispatchTime = DispatchTime.now() + dispatchDelay

        queue.asyncAfter(deadline: dispatchTime) {
            let when: DispatchTime = lastFireTime + dispatchDelay
            let now = DispatchTime.now()

            if now.rawValue >= when.rawValue {
                action(param)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

3.例子

在下面的示例中,您可以看到debouncing如何工作,使用字符串参数来标识调用.

let debouncedFunction = debounce(interval: 200, queue: DispatchQueue.main, action: { (identifier: String) in
    print("called: \(identifier)")
})

DispatchQueue.global(qos: .background).async {
    debouncedFunction("1")
    usleep(100 * 1000)
    debouncedFunction("2")
    usleep(100 * 1000)
    debouncedFunction("3")
    usleep(100 * 1000)
    debouncedFunction("4")
    usleep(300 * 1000) // waiting a bit longer than the interval
    debouncedFunction("5")
    usleep(100 * 1000)
    debouncedFunction("6")
    usleep(100 * 1000)
    debouncedFunction("7")
    usleep(300 * 1000) // waiting a bit longer than the interval
    debouncedFunction("8")
    usleep(100 * 1000)
    debouncedFunction("9")
    usleep(100 * 1000)
    debouncedFunction("10")
    usleep(100 * 1000)
    debouncedFunction("11")
    usleep(100 * 1000)
    debouncedFunction("12")
}
Run Code Online (Sandbox Code Playgroud)

注意:该usleep()功能仅用于演示目的,可能不是真正应用程序的最佳解决方案.

结果

当自上次呼叫以来至少有200ms的间隔时,您始终会收到回叫.

叫:4
叫:7
叫:12

  • 请参阅https://gist.github.com/simme/b78d10f0b29325743a18c905c5512788以获得正确的实施 (3认同)
  • 奇怪,但这不起作用。当我在字段中以短间隔输入字母时,当我什至没有完成所有文本输入时,我会得到这样的输出: `1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789, 1234567890` 函数间隔为 2000 ms所以我输入字母太慢并不是问题。 (2认同)

qui*_*yme 14

尽管这里有几个很好的答案,但我想我会分享我最喜欢的(纯 Swift)方法来消除用户输入的搜索......

1)添加这个简单的类(Debounce.swift):

import Dispatch

class Debounce<T: Equatable> {

    private init() {}

    static func input(_ input: T,
                      comparedAgainst current: @escaping @autoclosure () -> (T),
                      perform: @escaping (T) -> ()) {

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            if input == current() { perform(input) }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

2) 可选择包含此单元测试 ( DebounceTests.swift ):

import XCTest

class DebounceTests: XCTestCase {

    func test_entering_text_delays_processing_until_settled() {
        let expect = expectation(description: "processing completed")
        var finalString: String = ""
        var timesCalled: Int = 0
        let process: (String) -> () = {
            finalString = $0
            timesCalled += 1
            expect.fulfill()
        }

        Debounce<String>.input("A", comparedAgainst: "AB", perform: process)
        Debounce<String>.input("AB", comparedAgainst: "ABCD", perform: process)
        Debounce<String>.input("ABCD", comparedAgainst: "ABC", perform: process)
        Debounce<String>.input("ABC", comparedAgainst: "ABC", perform: process)

        wait(for: [expect], timeout: 2.0)

        XCTAssertEqual(finalString, "ABC")
        XCTAssertEqual(timesCalled, 1)
    }
}
Run Code Online (Sandbox Code Playgroud)

3)在任何你想延迟处理的地方使用它(例如UISearchBarDelegate):

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    Debounce<String>.input(searchText, comparedAgainst: searchBar.text ?? "") {
        self.filterResults($0)
    }
}
Run Code Online (Sandbox Code Playgroud)

基本前提是我们只是将输入文本的处理延迟了 0.5 秒。那时,我们将我们从事件中得到的字符串与搜索栏的当前值进行比较。如果它们匹配,我们假设用户已暂停输入文本,然后继续过滤操作。

因为它是通用的,所以它适用于任何类型的等值值。

由于 Dispatch 模块从第 3 版起就包含在 Swift 核心库中,因此该类也可以安全地用于非 Apple 平台


小智 9

如果您想保持整洁,这是一个基于GCD的解决方案,可以使用熟悉的基于GCD的语法来完成您需要的工作:https : //gist.github.com/staminajim/b5e89c6611eef81910502db2a01f1a83

DispatchQueue.main.asyncDeduped(target: self, after: 0.25) { [weak self] in
     self?.findPlaces()
}
Run Code Online (Sandbox Code Playgroud)

在上一次调用asyncDuped之后的0.25秒之后,只会一次调用一次 findPlaces()。


Fré*_*dda 5

我使用了这个受 Objective-C 启发的古老方法:

override func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    // Debounce: wait until the user stops typing to send search requests      
    NSObject.cancelPreviousPerformRequests(withTarget: self) 
    perform(#selector(updateSearch(with:)), with: searchText, afterDelay: 0.5)
}
Run Code Online (Sandbox Code Playgroud)

注意,被调用的方法updateSearch必须标记@objc!

@objc private func updateSearch(with text: String) {
    // Do stuff here   
}
Run Code Online (Sandbox Code Playgroud)

这种方法的一大优点是我可以传递参数(此处:搜索字符串)。对于这里介绍的大多数去抖动器来说,情况并非如此......


Fré*_*dda 5

首先,创建一个Debouncer泛型类:

//
//  Debouncer.swift
//
//  Created by Frédéric Adda

import UIKit
import Foundation

class Debouncer {

    // MARK: - Properties
    private let queue = DispatchQueue.main
    private var workItem = DispatchWorkItem(block: {})
    private var interval: TimeInterval

    // MARK: - Initializer
    init(seconds: TimeInterval) {
        self.interval = seconds
    }

    // MARK: - Debouncing function
    func debounce(action: @escaping (() -> Void)) {
        workItem.cancel()
        workItem = DispatchWorkItem(block: { action() })
        queue.asyncAfter(deadline: .now() + interval, execute: workItem)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后创建使用去抖动机制的UISearchBar的子类:

//
//  DebounceSearchBar.swift
//
//  Created by Frédéric ADDA on 28/06/2018.
//

import UIKit

/// Subclass of UISearchBar with a debouncer on text edit
class DebounceSearchBar: UISearchBar, UISearchBarDelegate {

    // MARK: - Properties

    /// Debounce engine
    private var debouncer: Debouncer?

    /// Debounce interval
    var debounceInterval: TimeInterval = 0 {
        didSet {
            guard debounceInterval > 0 else {
                self.debouncer = nil
                return
            }
            self.debouncer = Debouncer(seconds: debounceInterval)
        }
    }

    /// Event received when the search textField began editing
    var onSearchTextDidBeginEditing: (() -> Void)?

    /// Event received when the search textField content changes
    var onSearchTextUpdate: ((String) -> Void)?

    /// Event received when the search button is clicked
    var onSearchClicked: (() -> Void)?

    /// Event received when cancel is pressed
    var onCancel: (() -> Void)?

    // MARK: - Initializers
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        delegate = self
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        delegate = self
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
    }

    // MARK: - UISearchBarDelegate
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        onCancel?()
    }

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        onSearchClicked?()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        onSearchTextDidBeginEditing?()
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        guard let debouncer = self.debouncer else {
            onSearchTextUpdate?(searchText)
            return
        }
        debouncer.debounce {
            DispatchQueue.main.async {
                self.onSearchTextUpdate?(self.text ?? "")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,此类设置为UISearchBarDelegate。动作将作为闭包传递给此类。

最后,您可以像这样使用它:

class MyViewController: UIViewController {

    // Create the searchBar as a DebounceSearchBar
    // in code or as an IBOutlet
    private var searchBar: DebounceSearchBar?


    override func viewDidLoad() {
        super.viewDidLoad()

        self.searchBar = createSearchBar()
    }

    private func createSearchBar() -> DebounceSearchBar {
        let searchFrame = CGRect(x: 0, y: 0, width: 375, height: 44)
        let searchBar = DebounceSearchBar(frame: searchFrame)
        searchBar.debounceInterval = 0.5
        searchBar.onSearchTextUpdate = { [weak self] searchText in
            // call a function to look for contacts, like:
            // searchContacts(with: searchText)
        }
        searchBar.placeholder = "Enter name or email"
        return searchBar
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,在这种情况下,DebounceSearchBar已经是searchBar委托。你应该设置这个UIViewController子类的搜索栏委托!也不要使用委托函数。请使用提供的闭包!