【问题标题】:Cancel a URLSession based on user input in Swift在 Swift 中根据用户输入取消 URLSession
【发布时间】:2020-04-11 02:03:15
【问题描述】:

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

// 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 处理程序内部执行此操作?

【问题讨论】:

  • 看看下面的答案:Link
  • 次要的无关观察,但我们通常不会检查 200(例如 httpResponse.statusCode == 200,而是任何 2xx 代码(例如,200..&lt;300 ~= httpResponse.statusCode)。任何 2xx 代码都被视为成功,而不仅仅是 200 . 或许考虑到你目前的网络服务,200 就足够了,但 2xx 是谨慎的。

标签: ios swift urlsession


【解决方案1】:

基本思想是确保 API 返回一个可以在以后根据需要取消的对象,然后修改搜索例程以确保取消任何挂起的请求(如果有):

  1. 首先,让您的 API 调用返回 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 的角度来看,取消是一个错误,但从我们应用的角度来看,取消不是错误条件,而是预期的流程。

【讨论】:

  • 非常感谢您的帮助和您提供答案的结构化格式!!!这听起来正是我需要解决我的问题。我会尽快试一试的。
【解决方案2】:

您可以通过使用计时器来实现这一点,

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)
    }
}

【讨论】:

  • 感谢您的意见。也许我读错了,但我不明白这是如何真正取消任何旧请求。看起来它所做的只是在发射一个新的之前等待一段时间,而不是取消旧的并开始一个新的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-17
  • 2016-09-25
  • 2015-07-06
  • 2020-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多