ios - 为什么 searchBar "textDidChange"获取服务器请求并出错并崩溃

标签 ios swift uitableview search

抱歉,我是 swift 的初学者。
我有一个显示数据的 searchBar 和 tableView。
我还在 func“textDidChange”中调用 Api 到服务器。
但是,当我非常快速地输入或删除文本时,我的应用程序崩溃了,并且收到如下行所示的错误消息。
如何防止这种情况发生?
谢谢。

fatal error: Index out of range

override func viewDidLoad() {

  self.subscribe = contacts.notifySubject.subscribe({ json in
        self.tableView.reloadData()
    })
}

func doSearch() {
    if let word = searchBar.text {
        if word.isEmpty == false {
            contacts.searchFriend(word)
        }
    } else {
        tableView.reloadData()
    }
}

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

    if searchText.isEmpty == true {

        contacts.friends.removeAll(keepingCapacity: false)
        tableView.reloadData()
        return
    }else{

        doSearch()
    }
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    if contacts.friends.isEmpty == false {
        return contacts.friends.count
    }
    return 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


    if contacts.friends.isEmpty == false {

        guard contacts.friends.count != 0 else { return UITableViewCell() } // don't prevent crash

        let cell: SearchTableViewCell = SearchTableViewCell(style: .default, reuseIdentifier: "SearchTableViewCell")
        let user:User = contacts.friends[indexPath.row]
        cell.labelName.text = user.name
        return cell
    }

    let cell = UITableViewCell()

    return cell
}


class Contacts:Model {

   var friends:[User] = [User]()

   func searchFriend(_ word:String) {
    if word.isEmpty {
        return
    }
    self.friends.removeAll(keepingCapacity: false)
    var params:[String:Any] = [String:Any]()
    params["value"] = keyword
    api.searchUser.get( params, { json in

        json.forEach({ (index,data) in

            let user = User(data)
            if user.isExist {
                self.friends.append(user)
            }
        })
        self.notifySubject.onNext(json)
    })
}

} 

最佳答案

如果您在服务器上搜索文本更改,则意味着您正在连续调用 API。在第一个请求完成之前,您将发出下一个请求。因此,当您收到第一个请求的响应并重新加载 TableView 时,另一个请求再次完成,调用了 TableView 的重新加载。在这里,当 TableView 重新加载正在进行时,您从数组中删除对象,因此您的应用程序崩溃了。 您应该在进行新的 API 调用之前取消之前的 API 调用。 使用 NSOperation 和 NSOperationQueue 来实现此功能。

编辑

这是我用来从服务器搜索公交车站的示例 使用 AFNetworking 的 API 调用

 func getStops(sessionManager:AFHTTPSessionManager, parameters:[AnyHashable:Any],completionHandler:@escaping (_ status:Bool, _ responseObject:Any)->()){

        if !ReachabilityManager.shared.isReachable {
            let error = WSError()
            error.errorTitle = "Network error"
            error.errorDescription = "Unable to connect, please check your internet connectivity."
            completionHandler(false,error)
            return
        }

        self.showNetworkActivity()

        if let deviceId = SSKeychain.uniqueDeviceId(){
            sessionManager.requestSerializer.setValue(deviceId, forHTTPHeaderField: X_DEVICE_ID)
        }

        if let accessToken = DBManager.logged?.accessToken{
            sessionManager.requestSerializer.setValue(accessToken, forHTTPHeaderField: X_ACCESS_TOKEN)
        }

        if let simulatedContextId = self.simulatedContextId {
            sessionManager.requestSerializer.setValue(simulatedContextId, forHTTPHeaderField: X_SIMULATE_CONTEXT)
        }
        if let languageCode = NSLocale.current.languageCode{
            sessionManager.requestSerializer.setValue(languageCode, forHTTPHeaderField: X_LOCALE_ID)
        }
        let urlPath =  WSApi.apiVersion + "stops"

        sessionManager.get(urlPath, parameters: parameters, progress: { progress in

        }, success: {[unowned self] task, responseObject in
            self.hideNetworkActivity()
            self.getCurretnContext(task: task)

            var stops = [Stop]()

            if let array = responseObject as? [[AnyHashable:Any]]{
                stops.append(contentsOf: Stop.GetSports(array: array))
            }

            completionHandler(true,stops)

            }, failure: {[unowned self] operation, err in
                self.hideNetworkActivity()
                self.getCurretnContext(task: operation)
                let error = WSError(error: err as NSError)
                error.getStatusCode(operation: operation)
                if error.isUnauthorised{
                    AppDelegate.shared.handleUnAuthorised(error: error)
                    return
                }
                completionHandler(false,error)

        })
    }

ViewController 实现

 protocol StopSearchViewControllerDelegate : NSObjectProtocol {

    /**
     * Called when a place has been selected from the available autocomplete predictions.
     * @param viewController The |StopSearchViewController| that generated the event.
     * @param stop The |Stop| that was returned.
     */
    func viewController(_ viewController: StopSearchViewController, didAutocompleteWith stop: Stop)

    /**
     * Called when a non-retryable error occurred when retrieving autocomplete predictions or place
     * details. A non-retryable error is defined as one that is unlikely to be fixed by immediately
     * retrying the operation.
     * All other error codes are non-retryable.
     * @param viewController The |StopSearchViewController| that generated the event.
     * @param error The |WSError| that was returned.
     */
    func viewController(_ viewController: StopSearchViewController, didFailAutocompleteWithWSError error: WSError)


    /**
     * Called when the user taps the Cancel button in a |StopSearchViewController|.
     * @param viewController The |StopSearchViewController| that generated the event.
     */
    func autocompleteWasCancelled(_ viewController: StopSearchViewController)
}

class StopSearchViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var searchBar: UISearchBar!

    weak var delegate:StopSearchViewControllerDelegate?

    fileprivate var stops = [Stop]()

    fileprivate var isNavigationBarHidden = false

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.tableView.tableHeaderView = UIView()
        self.searchBar.becomeFirstResponder()

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.isNavigationBarHidden = self.navigationController?.isNavigationBarHidden ?? false
        self.navigationController?.isNavigationBarHidden = true
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.navigationController?.isNavigationBarHidden = isNavigationBarHidden
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    fileprivate func loadStops(parameters:[AnyHashable:Any]){
        WSApi.sharedManager.operationQueue.cancelAllOperations()
        WSApi.shared.getStops(sessionManager: WSApi.sharedManager, parameters: parameters) {[weak self] (status, responseObject) in
            if let strongSelf = self {
                strongSelf.stops.removeAll()
                if let stops = responseObject as? [Stop]{
                    if !strongSelf.searchBar.text!.isEmpty{
                        strongSelf.stops.append(contentsOf: stops)
                    }
                }
                strongSelf.tableView.reloadData()
            }
        }
    }

    fileprivate func closeViewController(){
        if let navigationController = self.navigationController{
            navigationController.popViewController(animated: true)
        }else{
            self.dismiss(animated: true, completion: nil)
        }
    }

    override var preferredStatusBarStyle: UIStatusBarStyle{
        return .lightContent
    }

    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
        return .none
    }

    override var prefersStatusBarHidden: Bool{
        return false
    }

}

extension StopSearchViewController :UITableViewDataSource{

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return stops.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: StopSearchCell.reuseIdentifier, for: indexPath) as! StopSearchCell
        let stop = stops[indexPath.row]
        cell.configureCell(stop: stop)
        return cell
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 0
    }

    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 20
    }

    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let attrs = [NSFontAttributeName : UIFont(name: "Helvetica", size: 14)!, NSForegroundColorAttributeName:UIColor.lightGray]
        let attributedString = NSMutableAttributedString(string:"powered by ", attributes:attrs)
        let bAttrs = [NSFontAttributeName : UIFont(name: "Helvetica", size: 15)!, NSForegroundColorAttributeName:UIColor.darkGray]
        let boldString = NSMutableAttributedString(string:"Change Transit", attributes:bAttrs)
        attributedString.append(boldString)
        let frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44)
        let label = UILabel(frame: frame)
        label.textAlignment = .center
        label.attributedText = attributedString
        return label
    }


}

extension StopSearchViewController :UITableViewDelegate{

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let stop = stops[indexPath.row]
        self.delegate?.viewController(self, didAutocompleteWith: stop)
       // self.closeViewController()
    }

}

extension StopSearchViewController :UISearchBarDelegate{


    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//        guard !searchText.isEmpty else {
//            WSApi.sharedManager.operationQueue.cancelAllOperations()
//            self.stops.removeAll()
//            self.tableView.reloadData()
//            return
//        }
        let parameters = ["data_set_id":WSApi.shared.currentContextId ?? "","search_text": searchText]
        self.loadStops(parameters: parameters)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.delegate?.autocompleteWasCancelled(self)
       // self.closeViewController()
    }

}

关于ios - 为什么 searchBar "textDidChange"获取服务器请求并出错并崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47345518/

相关文章:

swift - 在 macOS 上使用 Swift 4 检测 USB 设备

ios - UITableViewCells 显示在其他类型的单元格上

ios - FBFriendPickerDelegate 在 Facebook v4 中消失

ios - 将离线数据保存到 CB Lite 2.0

ios - 在 Storyboard 中定义的模拟 UIViewController

ios - 如何 initWithNib 派生子类

ios - 默认情况下直接添加到数组

ios - 备份和接收核心数据 SQlite3 数据库

ios - 延迟一定时间后重复短促的声音

iOS,带有 Collection View 的自定义键盘,约束问题