Swift:解码API响应时获得零

Swift:解码API响应时获得零,swift,api,networking,jsondecoder,Swift,Api,Networking,Jsondecoder,我在解码API响应时遇到问题 因此,我们有一个NetworkManager类,用于解码API。我有一个简单的GET端点,需要从中检索机场列表。以下是端点: static let airports = Endpoint(url: "/test/airports") 端点定义如下: public struct Endpoint : Equatable { public init(url: String? = nil, pattern: String? = nil, me

我在解码API响应时遇到问题

因此,我们有一个NetworkManager类,用于解码API。我有一个简单的GET端点,需要从中检索机场列表。以下是端点:

static let airports = Endpoint(url: "/test/airports")
端点定义如下:

public struct Endpoint : Equatable {
    public init(url: String? = nil, pattern: String? = nil, methods: [Test.HTTPMethod] = [.get], type: Test.EncodingType = .json)
}
struct Airport: Codable {
    let id: String
    let name: String
    let iata3: String
    let icao4: String
    let countryCode: String
}
在我们的网络管理器中,我们有:

   public func call<R: Decodable>(_ endpoint: Endpoint,
                        with args: [String: String]? = nil,
                        using method: HTTPMethod = .get,
                        expecting response: R.Type?,
                        completion: APIResponse<R>) {
    call(endpoint, with: args, parameters: Nothing(),
         using: method, posting: Nothing(), expecting: response, completion: completion)
}
然后我调用端点,如:

private func getAirportsList() {
    API.client.call(.airports, expecting: [Airport].self) { (result, airports) in
        print(airports)
    }
}
现在,我使用Charles代理,我得到了预期的响应:

[{
    "id": "5f92b0269c983567fc4b9683",
    "name": "Amsterdam Schiphol",
    "iata3": "AMS",
    "icao4": "EHAM",
    "countryCode": "NL"
}, {
    "id": "5f92b0269c983567fc4b9685",
    "name": "Bahrain International",
    "iata3": "BAH",
    "icao4": "OBBI",
    "countryCode": "BH"
}, {
    "id": "5f92b0269c983567fc4b968b",
    "name": "Bankstown",
    "iata3": "BWU",
    "icao4": "YSBK",
    "countryCode": "AU"
}]
但是在我的getAirports()方法中,airports是零。我真的很想知道为什么。很明显,端点被正确命中,但我的解码失败了

编辑:

完整方法:

private func call<P: Encodable, B: Encodable, R: Decodable>(_ endpoint: Endpoint,
                                                                with args: [String: String]? = nil,
                                                                parameters params: P?,
                                                                using method: HTTPMethod = .get,
                                                                posting body: B?,
                                                                expecting responseType: R.Type?,
                                                                completion: APIResponse<R>) {

        // Prepare our URL components

        guard var urlComponents = URLComponents(string: baseURL.absoluteString) else {
            completion?(.failure(nil, NetworkError(reason: .invalidURL)), nil)
            return
        }

        guard let endpointPath = endpoint.url(with: args) else {
            completion?(.failure(nil, NetworkError(reason: .invalidURL)), nil)
            return
        }

        urlComponents.path = urlComponents.path.appending(endpointPath)

        // Apply our parameters

        applyParameters: if let parameters = try? params.asDictionary() {
            if parameters.count == 0 {
                break applyParameters
            }

            var queryItems = [URLQueryItem]()

            for (key, value) in parameters {
                if let value = value as? String {
                    let queryItem = URLQueryItem(name: key, value: value)
                    queryItems.append(queryItem)
                }
            }

            urlComponents.queryItems = queryItems
        }

        // Try to build the URL, bad request if we can't

        guard let urlString = urlComponents.url?.absoluteString.removingPercentEncoding,
            var url = URL(string: urlString) else {
                completion?(.failure(nil, NetworkError(reason: .invalidURL)), nil)
                return
        }
        
        if let uuid = UIDevice.current.identifierForVendor?.uuidString, endpoint.pattern == "/logging/v1/device/<device_id>" {
            let us = "http://192.168.6.128:3000/logging/v1/device/\(uuid)"
            guard let u = URL(string: us) else { return }
            url = u
        }

        // Can we call this method on this endpoint? If not, lets not try to continue

        guard endpoint.httpMethods.contains(method) else {
            completion?(.failure(nil, NetworkError(reason: .methodNotAllowed)), nil)
            return
        }
        
        // Apply debug cookie
        
        if let debugCookie = debugCookie {
            HTTPCookieStorage.shared.setCookies(
                HTTPCookie.cookies(
                    withResponseHeaderFields: ["Set-Cookie": debugCookie],
                    for:url
            ), for: url, mainDocumentURL: url)
        }

        // Build our request

        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue

        if let headers = headers {
            for (key, value) in headers {
                request.setValue(value, forHTTPHeaderField: key)
            }
        }

        // If we are posting, safely retrieve the body and try to assign it to our request

        if !(body is NothingProtocol) {
            guard let body = body else {
                completion?(.failure(nil, NetworkError(reason: .buildingPayload)), nil)
                return
            }

            do {
                let result = try encode(body: body, type: endpoint.encodingType)
                request.httpBody = result.data
                request.setValue(result.headerValue, forHTTPHeaderField: "Content-Type")
            } catch {
                completion?(.failure(nil, NetworkError(reason: .buildingPayload)), nil)
                return
            }
        }
        
        // Build our response handler
        
        let task = session.dataTask(with: request as URLRequest) { (rawData, response, error) in

            // Print some logs to help track requests
            
            var debugOutput = "URL\n\(url)\n\n"
            
            if !(params is Nothing.Type) {
                debugOutput.append(contentsOf: "PARAMETERS\n\(params.asJSONString() ?? "No Parameters")\n\n")
            }
            
            if !(body is Nothing.Type) {
                debugOutput.append(contentsOf: "BODY\n\(body.asJSONString() ?? "No Body")\n\n")
            }
            
            if let responseData = rawData {
                debugOutput.append(contentsOf: "RESPONSE\n\(String(data: responseData, encoding: .utf8) ?? "No Response Content")")
            }
            
            Logging.client.record(debugOutput, domain: .network, level: .debug)

            guard let httpResponse = response as? HTTPURLResponse else {
                guard error == nil else {
                    completion?(.failure(nil, NetworkError(reason: .unwrappingResponse)), nil)
                    return
                }

                completion?(.failure(nil, NetworkError(reason: .invalidResponseType)), nil)
                return
            }

            let statusCode = httpResponse.statusCode

            // We have an error, return it

            guard error == nil, NetworkManager.successStatusRange.contains(statusCode) else {
                var output: Any?

                if let data = rawData {
                    output = (try? JSONSerialization.jsonObject(with: data,
                                                                options: .allowFragments)) ?? "Unable to connect"
                    
                    Logging.client.record("Response: \(String(data: data, encoding: .utf8) ?? "No error data")", domain: .network)
                }

                completion?(.failure(statusCode, NetworkError(reason: .requestFailed, json: output)), nil)
                return
            }

            // Safely cast the responseType we are expecting

            guard let responseType = responseType else {
                completion?(.failure(statusCode, NetworkError(reason: .castingToExpectedType)), nil)
                return
            }

            // If we are expecting nothing, return now (since we will have nothing!)

            if responseType is Nothing.Type {
                completion?(.success(statusCode), nil)
                return
            }

            guard let data = rawData else {
                assertionFailure("Could not cast data from payload when we passed pre-cast checks")
                return
            }

            // Decode the JSON and cast to our expected response type

            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .iso8601
                let responseObject = try decoder.decode(responseType, from: data)
                completion?(.success(statusCode), responseObject)
                return
            } catch let error {
                let content = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
                Logging.client.record("Failed to build codable from JSON: \(String(describing: content))\n\nError: \(error)", domain: .network, level: .error)
                assertionFailure("Failed to build codable from JSON: \(error)")
                completion?(.failure(statusCode, NetworkError(reason: .castingToExpectedType)), nil)
                return
            }
        }

        // Submit our request

        task.resume()
    }
private func调用(uEndpoint:endpoint,
带args:[String:String]?=nil,
参数参数:P?,
使用方法:HTTPMethod=.get,
发帖主体:B?,
应为响应类型:R.类型?,
完成:API(回应){
//准备我们的URL组件
guard var urlComponents=urlComponents(字符串:baseURL.absoluteString)else{
完成?(.failure(无,网络错误(原因:.invalidURL)),无)
返回
}
guard let endpointPath=endpoint.url(带:args)else{
完成?(.failure(无,网络错误(原因:.invalidURL)),无)
返回
}
urlComponents.path=urlComponents.path.appending(endpointPath)
//应用我们的参数
applyParameters:if let parameters=try?params.asDictionary(){
如果parameters.count==0{
断开应用参数
}
var queryItems=[URLQueryItem]()
用于参数中的(键、值){
如果let value=值为?字符串{
让queryItem=URLQueryItem(名称:key,值:value)
追加(queryItem)
}
}
urlComponents.queryItems=queryItems
}
//尝试构建URL,如果我们不能,则请求错误
guard let urlString=urlComponents.url?.absoluteString.removingPercentEncoding,
var url=url(字符串:urlString)else{
完成?(.failure(无,网络错误(原因:.invalidURL)),无)
返回
}
如果让uuid=UIDevice.current.identifierForVendor?.UUIString,endpoint.pattern==“/logging/v1/device/”{
让我们=”http://192.168.6.128:3000/logging/v1/device/\(uuid)
guard let u=URL(字符串:us)else{return}
url=u
}
//我们可以在此端点上调用此方法吗?如果不能,我们就不要尝试继续
保护端点.httpMethods.contains(方法)else{
完成?(.failure(无,网络错误(原因:.methodNotAllowed)),无)
返回
}
//应用调试cookie
如果让debugCookie=debugCookie{
HTTPCookieStorage.shared.setCookies(
HTTPCookie.cookies(
withResponseHeaderFields:[“设置Cookie”:调试Cookie],
地址:url
),for:url,main文档url:url)
}
//构建我们的请求
var-request=URLRequest(url:url)
request.httpMethod=method.rawValue
如果let headers=headers{
用于标题中的(键、值){
request.setValue(值,forHTTPHeaderField:key)
}
}
//如果我们正在发布,请安全地检索正文并尝试将其分配给我们的请求
如果!(身体什么都不是协议){
防护罩让身体=身体其他部位{
完成?(.failure(无,网络错误(原因:.buildingPayload)),无)
返回
}
做{
让结果=尝试编码(正文:正文,类型:endpoint.encodingType)
request.httpBody=result.data
request.setValue(result.headerValue,forHTTPHeaderField:“内容类型”)
}抓住{
完成?(.failure(无,网络错误(原因:.buildingPayload)),无)
返回
}
}
//构建我们的响应处理器
让task=session.dataTask(其中:request作为URLRequest){(rawData,response,error)在
//打印一些日志以帮助跟踪请求
var debugOutput=“URL\n\(URL)\n\n”
if!(参数为Nothing.Type){
debugOutput.append(内容为:“参数\n\(params.asJSONString()?“无参数”)\n\n)
}
if!(主体为Nothing.Type){
debugOutput.append(内容为:“BODY\n\(BODY.asJSONString()??“无BODY”)\n\n)
}
如果let responseData=rawData{
debugOutput.append(内容为:“响应\n\(字符串(数据:响应数据,编码:.utf8)??“无响应内容”))
}
Logging.client.record(debugOutput,域:。网络,级别:。调试)
guard let httpResponse=响应为?HTTPURLResponse else{
保护错误==nil else{
完成?(.failure(无,网络错误(原因:.unwrappingResponse)),无)
返回
}
完成?(.failure(无,网络错误(原因:.invalidResponseType)),无)
返回
}
让statusCode=httpResponse.statusCode
//我们有一个错误,返回它
guard error==nil,NetworkManager.successStatusRange.contains(statusCode)else{
var输出:有吗?