Home > database >  Problem making API request with async / await in swift 5.5
Problem making API request with async / await in swift 5.5

Time:10-14

I have a problem when I want to refactor my API requests to the new async / await functionality in swift 5.5.

My code is (I remove all credentials and personal information, but the logic is the same):

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
}

The code I am trying to refactor, the original in the old way, is:

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

The problem is, that with the new code, I have this error:

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

I have absolutely no clue what is happening here, and to be more confused, the error is not happening always, most of the times, but sometimes the code executes correctly. What is the problem for this behaviour and this error? PD: the original code, before changing to async / await works absolutely fine always

The way I call the method, is on the appear of the main view:

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

CodePudding user response:

Cancellation errors for async URL session data tasks occur when the parent task is cancelled, for example if MyView is removed from the view hierarchy. Is this what is happening?

  • Related