Проблема с выполнением запроса API с асинхронным / ожиданием в swift 5.5

#swift #swiftui #swift5.5

Вопрос:

У меня возникла проблема, когда я хочу преобразовать свои запросы API в новую функциональность async / await в swift 5.5.

Мой код (я удаляю все учетные данные и личную информацию, но логика та же):

 class API {
    enum HTTPMethods: String {
        case GET = "GET"
        case POST = "POST"
    }
    
    // Credentials
    private let testKey = "abcdef"
    private var authenticationHeaders: [String: String] = ["username": "myUsername",
                                                           "password": "myPassword"]
    private var token: String = "MyToken"
    
    // Data collected from the API requests
    private var a: Response1?
    private var b: Response2?
    
    // Base URLs
    private var url = "https://example.com"
    
    // Singleton
    static let singleton = API()
    private init() {
        // Increasing the interval for the timeout
        URLSession.shared.configuration.timeoutIntervalForRequest = 530.0
        URLSession.shared.configuration.timeoutIntervalForResource = 560.0
    }
    
    private func getRequest(url: URL, method: HTTPMethods) -> URLRequest{
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("Token (token)", forHTTPHeaderField: "Authorization")
        return request
    }
    
    private func checkResponse(response: URLResponse?, nameRequest: String){
        if let httpResponse = response as? HTTPURLResponse {
            switch httpResponse.statusCode {
            case 200...201:
                print("URL request made successfully!")
            default:
                fatalError("Error in the request (nameRequest), status code (httpResponse.statusCode), response: (String(describing: response))")
            }
        }
    }
    
    func makeAuthorization() async {
        let url = URL(string: url)!
        var request = getRequest(url: url, method: HTTPMethods.POST)
        // Insert json data to the request
        if let jsonData = try? JSONSerialization.data(withJSONObject: authenticationHeaders) {
            request.httpBody = jsonData
        }
        do {
            let (data, response) = try await URLSession.shared.data(for: request)
            if let response = try? JSONDecoder().decode(Response1.self, from: data) {
                token = response.token
            }
            checkResponse(response: response, nameRequest: "Authorization")
        } catch {
            fatalError("Request failed with error: (error)")
        }
    }
    
    func getTestConfiguration() async {
        let url = URL(string: url)!
        let request = getRequest(url: url, method: HTTPMethods.GET)
        do {
            let (data, response) = try await URLSession.shared.data(for: request)
            if let response = try? JSONDecoder().decode(Response2.self, from: data) {
                self.b = response
            }
            checkResponse(response: response, nameRequest: "getTestConfiguration")
        } catch {
            fatalError("Request failed with error: (error)")
        }
    }
    
}


struct Response1: Codable {
    let token: String
}


struct Response2: Codable {
    let token: String
}
 

Код, который я пытаюсь переработать, оригинал по-старому, это:

 func makeAuthorizationO() {
        if let urlObject = URL(string: url) {
            var request = getRequest(url: urlObject, method: HTTPMethods.POST)
            
            // Insert json data to the request
            if let jsonData = try? JSONSerialization.data(withJSONObject: authenticationHeaders) {
                request.httpBody = jsonData
            }
            
            URLSession.shared.dataTask(with: request) { [self] data, response, error in
                guard error == nil,
                      let _ = data else {
                          print(error ?? "Error in makeAuthorization, but error is nil")
                          return
                      }
                if let unwrappedData = data {
                    if let response = try? JSONDecoder().decode(Response1.self, from: unwrappedData) {
                        token = response.token
                        print("makeAuthorization successful!")
                    }
                }
                checkResponse(response: response, nameRequest: "Authorization")
            }.resume()
        }
    }
    
    func getTestConfigurationO(){
        if let urlObject = URL(string: url) {
            URLSession.shared.dataTask(with: getRequest(url: urlObject, method: HTTPMethods.GET)) { data, response, error in
                guard error == nil,
                      let _ = data else {
                          print(error ?? "Error in getTestConfiguration, but error is nil")
                          return
                      }
                if let unwrappedData = data {
                    let decoder = JSONDecoder()
                    if let test = try? decoder.decode(Response2.self, from: unwrappedData) {
                        self.b = test
                    }
                }
                self.checkResponse(response: response, nameRequest: "TestConfiguration")
            }.resume()
        }
    }
 

Проблема в том, что с новым кодом у меня возникла эта ошибка:

 Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://example.com, NSErrorFailingURLKey=https://example.com, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <3B064821-156C-481C-8A72-30BBDEE5218F>.<3>"
 

Я абсолютно не понимаю, что здесь происходит, и, чтобы еще больше запутаться, ошибка происходит не всегда, в большинстве случаев, но иногда код выполняется правильно. В чем проблема такого поведения и этой ошибки? PD: исходный код, перед переходом на асинхронность / ожидание, всегда работает абсолютно нормально

Способ, которым я вызываю метод, находится на экране главного представления:

 struct MyView: View {
    var body: some View {
        VStack {
            Text("My message")
        }.task {
            let api = API.singleton
            await api.makeAuthorization()
            await api.getTestConfiguration()
        }
    }
}
 

Комментарии:

1. Ошибки отмены для задач с данными сеанса URL возникают при отмене родительской задачи, например, если MyView удален из иерархии представлений. Неужели это происходит?

2. Да, это именно моя проблема. Из-за упрощения кода я пропускаю ту часть, где я меняю представление, но вы правы, теперь это работает. Спасибо!!

3. Хорошо, отлично! Тогда я добавлю это в качестве ответа.

Ответ №1:

Ошибки отмены для задач с данными сеанса асинхронного URL-адреса возникают при отмене родительской задачи, например, если MyView удален из иерархии представлений. Это то, что происходит?