根据 Swift 中的用户输入取消 URLSession

Cancel a URLSession based on user input in Swift

我已将对我的应用程序的 API 调用集中在名为 APIService 的 class 中。 调用如下所示:

// GET: Attempts getconversations API call. Returns Array of Conversation objects or Error
    func getConversations(searchString: String = "", completion: @escaping(Result<[Conversation], APIError>) -> Void) {

        {...} //setting up URLRequest

        let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let _ = data
                else {
                    print("ERROR: ", error ?? "unknown error")
                    completion(.failure(.responseError))
                    return
            }
            do {

                {...} //define custom decoding strategy

                }
                let result = try decoder.decode(ResponseMultipleElements<[Conversation]>.self, from: data!)
                completion(.success(result.detailresponse.element))
            }catch {
                completion(.failure(.decodingError))
            }
        }
        dataTask.resume()
    }

我正在从应用程序中的任何位置执行 API 调用,如下所示:

func searchConversations(searchString: String) {
        self.apiService.getConversations(searchString: searchString, completion: {result in
            switch result {
            case .success(let conversations):
                DispatchQueue.main.async {
                    {...} // do stuff 
                }
            case .failure(let error):
                print("An error occured \(error.localizedDescription)")
            }
        })
    }

我现在想实现的是,用户在输入searchString时每点击一个字符,就执行func searchConversations。 只需根据 UIPressesEvent 被触发调用 func searchConversations 就足够简单了。像这样:

override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
        guard let key = presses.first?.key else { return }

        switch key.keyCode {
        {...} // handle special cases
        default:
            super.pressesEnded(presses, with: event)
            searchConversations(searchString: SearchText.text)
        }
    }

我现在的问题是: 每当输入一个新字符时,我想取消以前的 URLSession 并启动一个新字符。我怎样才能从 UIPressesEvent 处理程序中做到这一点?

您可以使用计时器来实现,

1) 定义一个定时器变量

var requestTimer: Timer?

2) 更新 searchConversations 函数

@objc func searchConversations() {
    self.apiService.getConversations(searchString: SearchText.text, completion: {result in
        switch result {
        case .success(let conversations):
            DispatchQueue.main.async {
                {...} // do stuff
            }
        case .failure(let error):
            print("An error occured \(error.localizedDescription)")
        }
    })
}

3) 更新 pressesEnded

override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {

    guard let key = presses.first?.key else { return }

    switch key.keyCode {
    {...} // handle special cases
    default:
        super.pressesEnded(presses, with: event)
        self.requestTimer?.invalidate()
        self.requestTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(searchConversations), userInfo: nil, repeats: false)
    }
}

基本思想是确保 API return 是一个以后可以取消的对象,如果需要的话,然后修改搜索例程以确保取消任何待处理的请求,如果有:

  1. 首先,API 调用 return URLSessionTask 对象:

    @discardableResult
    func getConversations(searchString: String = "", completion: @escaping(Result<[Conversation], APIError>) -> Void) -> URLSessionTask {
        ...
    
        let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
            ...
        }
        dataTask.resume()
        return dataTask
    }
    
  2. 让您的搜索例程跟踪上一个任务,如果需要则取消它:

    private weak var previousTask: URLSessionTask?
    
    func searchConversations(searchString: String) {
        previousTask?.cancel()
        previousTask = apiService.getConversations(searchString: searchString) { result in
            ...
        }
    }
    
  3. 我们经常添加一个微小的延迟,以便在用户快速输入时避免大量不必要的网络请求:

    private weak var previousTask: URLSessionTask?
    private weak var delayTimer: Timer?
    
    func searchConversations(searchString: String) {
        previousTask?.cancel()
        delayTimer?.invalidate()
    
        delayTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { [weak self] _ in
            guard let self = self else { return }
            self.previousTask = self.apiService.getConversations(searchString: searchString) {result in
                ...
            }
        }
    }
    
  4. 唯一的另一件事是您可能想要更改网络请求错误处理程序,以便不会像错误一样处理请求的“取消”。从URLSession的角度来看,取消是一个错误,但从我们的应用程序的角度来看,取消不是一个错误条件,而是一个预期的流程。