如果您在搜索栏 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
}
}
注:未测试
一)当用户在搜索栏中键入内容时,会触发一个 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
}
}
注:未测试