首页 > 解决方案 > 在 swift 5.5 中使用 async / await 发出 API 请求时出现问题

问题描述

当我想将我的 API 请求重构为 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:原始代码,在更改为 async / await 之前总是可以正常工作

我调用该方法的方式是在主视图的出现上:

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

标签: swiftswiftuiswift5.5

解决方案


取消父任务时会发生异步 URL 会话数据任务的取消错误,例如,如果从视图层次结构中删除 MyView。这是正在发生的事情吗?


推荐阅读