Отмена URLSession на основе пользовательского ввода в Swift - PullRequest
0 голосов
/ 10 апреля 2020

Я централизовал вызовы 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 из любой точки приложения: 1008 * за каждый введенный пользователем символ при вводе searchString. Это было бы достаточно просто, просто позвонив по номеру func searchConversations в зависимости от срабатывания UIPressesEvent. Вот так:

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?

Ответы [ 2 ]

0 голосов
/ 10 апреля 2020

Основная идея c состоит в том, чтобы убедиться, что 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 отмена - это ошибка, но с точки зрения нашего приложения отмена - это не условие ошибки, а ожидаемый поток.

0 голосов
/ 10 апреля 2020

Этого можно добиться с помощью таймера,

1) Определить переменную таймера

var requestTimer: Timer?

2) Обновить searchConversations function

@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) Обновление presssesEnded

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