如果您在搜索栏 API 中输入的速度太快,则调用错误

If you type too fast in search-bar API call errors out

我这里有两个问题:

一)当用户在搜索栏中键入内容时,会触发一个 api 调用来更新表格视图。如果用户键入太快,则会导致错误。我想看看如何防止这种情况。

二) 我只接到 500 个免费 api 电话,当用户键入一个新的 API 时,被解雇确实可以相当快地增加请求。有没有一种方法可以保存结果并短暂存储以防止多次重复 API 调用?这不是正确的做法吗?

这是地址结果文件

// MARK: - AddressResult
struct AddressResult: Codable {
    let meta: Meta
    let autocomplete: [Autocomplete]
}

// MARK: - Autocomplete
struct Autocomplete: Codable {
    let areaType, id: String
    let score: Double
    let mprID: String?
    let fullAddress: [String]?
    let line: String?
    let city: String
    let postalCode: String?
    let stateCode, country: String
    let centroid: Centroid?
    let propStatus, validationCode: [String]?
    let counties: [County]?
    let slugID, geoID: String?
    let countyNeededForUniq: Bool?

    enum CodingKeys: String, CodingKey {
        case areaType = "area_type"
        case id = "_id"
        case score = "_score"
        case mprID = "mpr_id"
        case fullAddress = "full_address"
        case line, city
        case postalCode = "postal_code"
        case stateCode = "state_code"
        case country, centroid
        case propStatus = "prop_status"
        case validationCode = "validation_code"
        case counties
        case slugID = "slug_id"
        case geoID = "geo_id"
        case countyNeededForUniq = "county_needed_for_uniq"
    }
}

// MARK: - Centroid
struct Centroid: Codable {
    let lon, lat: Double
}

// MARK: - County
struct County: Codable {
    let name, fips, stateCode: String

    enum CodingKeys: String, CodingKey {
        case name, fips
        case stateCode = "state_code"
    }
}

// MARK: - Meta
struct Meta: Codable {
    let build: String
}

这是 AddressTableViewCell

import UIKit

class AddressTableViewCell: UITableViewCell {
    
    @IBOutlet weak var addressLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
}

这里是 ViewController:

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
    
    // MARK: - Variable Declarations
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var searchBar: UISearchBar!
    
    var tempAddressData: [String] = []
    var searchString = ""
    
    
    // MARK: - ViewController LifeCycle Methods
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        initSearchController()
    }
    

    // MARK: - SearchBar Methods
    func initSearchController() {
        searchBar.delegate = self
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        
        if searchText == "" {
            tempAddressData = []
        } else {
            searchString = searchText
            fetchAddresses()
            tempAddressData = []
        }
        tableView.reloadData()
    }
    
    
    // MARK: - TableView Methods
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tempAddressData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "address", for: indexPath) as? AddressTableViewCell else {
            return UITableViewCell()
        }
        
        cell.addressLabel.text = tempAddressData[indexPath.row]
        
        return cell
    }

    // MARK: - API Method
    // TODO: - This will be moved to "AddressFetcher" when it is compleated.
    func fetchAddresses() {
        
        let escapedString = searchString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
        
        //Create URL:
        guard let url = URL(string: "https://realty-in-us.p.rapidapi.com/locations/auto-complete?input=\(escapedString ?? "")") else {
            fatalError("Invalid url string.")
        }
        
        //create request to add headers:
        var request = URLRequest.init(url: url)
        request.httpMethod = "GET"
        let config = URLSessionConfiguration.default
        config.httpAdditionalHeaders = ["Content-Type" : "application/json", "X-RapidAPI-Host" : "realty-in-us.p.rapidapi.com", "X-RapidAPI-Key":"API_KEY"]
        let session = URLSession.init(configuration: config)
        
        
        //Create URL session data task
        let task = session.dataTask(with: url) { data, _, error in
            guard let data = data, error == nil else {
                fatalError("Unable to unwrap date from api call.")
            }
            
            do {
                //Parse the JSON data
                let autoCompleteResult = try JSONDecoder().decode(AddressResult.self, from: data)
                //print("Successfully received the data \(autoCompleteResult.autocomplete)")
                DispatchQueue.main.async {
                    for address in autoCompleteResult.autocomplete {
                        self.tempAddressData.append("\(address.line ?? "") \(address.city), \(address.stateCode) \(address.postalCode ?? "")")
                        self.tableView.reloadData()
                    }
                }
            } catch {
                fatalError(error.localizedDescription)
            }
        }
        task.resume()
    }
}

正如其他人提到的那样,我认为在每次笔划时查询 api 是一种不好的做法,使用 Debouncer 将其用作蓝图根据您自己的需要进行调整。

顺便说一句,像 reactiveKit 这样的库内置了 Debouncer。易于使用。

class 去抖器 {

var handler: (() -> Void)? {
    didSet {
        worker?.cancel()
        
        if let handler = handler {
            let worker = DispatchWorkItem(block: handler)
            queue.asyncAfter(deadline: .now() + timeInterval, execute: worker)
            self.worker = worker
        }
    }
}

private let timeInterval: TimeInterval
private var worker: DispatchWorkItem?
private let queue: DispatchQueue

init(timeInterval: TimeInterval, queue: DispatchQueue = .main) {
    self.timeInterval = timeInterval
    self.queue = queue
}

func cancel() {
    worker?.cancel()
    worker = nil
}

}

class节流阀{

var handler: (() -> Void)? {
    didSet {
        if worker == nil {
            let worker = DispatchWorkItem { [weak self] in
                self?.handler?()
                self?.worker = nil
            }
            
            self.worker = worker
            queue.asyncAfter(deadline: .now() + timeInterval, execute: worker)
        }
    }
}

private let timeInterval: TimeInterval
private var worker: DispatchWorkItem?
private let queue: DispatchQueue

init(timeInterval: TimeInterval, queue: DispatchQueue = .main) {
    self.timeInterval = timeInterval
    self.queue = queue
}

func cancel() {
    worker?.cancel()
    worker = nil
}

}

结合框架的示例解决方案

import Combine

class ViewModelSearch: ObservableObject {
    @Published var searchText = ""
    
}

class YourVC:UIViewController {
    
    var viewModel = ViewModelSearch()
    private var subscriptions = Set<AnyCancellable>()
    
    
    override func viewDidLoad() {
        
        viewModel.$searchText
            .debounce(for: .seconds(1), scheduler: DispatchQueue.main)
            .sink { value in
                //CALL YOUR API
            }.store(in: &subscriptions)
    }
    
    func searchTextUpdated(newText:String) {
        viewModel.searchText = newText
    }
}

注:未测试