来自 Xcode 中的 API 的不稳定错误消息?

Unstable error message from the API in Xcode?

当我尝试使用 AlamofirePromiseKit decode 一些 json 数据时,我遇到了一些不稳定的 error 消息..为什么我我说“不稳定 error 消息是有时当我点击 API .GET 请求时,我得到完整格式的响应,没有任何错误,但在几次之后 运行宁 application Xcode 抛出一个 Alamofire Response Serialization Error 这太令人困惑了......我也实现了 Coding keys,数据和 json response 格式也在正确的格式.. 谁能帮我解码这个 error message .?

这是我在随机应用程序 运行 中从 API 得到的响应:

在下一个 运行 之后,这里是 xcode console 中的 error message

Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))))

如果需要对代码的任何部分进行更多分析,请告诉我,以便我使用所需的代码片段更新问题内容..

这是我的 json 响应模型结构:

// MARK: - TickerByPair
struct TickerByPair {
    var price, ask: Double
    var askVolume: Double
    var bid: Double
    var bidVolume, volume: Double
    var time: String
    
}

extension TickerByPair: Decodable{
    
    enum TrackCodingKeys: String, CodingKey {
        case price = "price"
        case ask = "ask"
        case askVolume = "askVolume"
        case bid = "bid"
        case bidVolume = "bidVolume"
        case volume = "volume"
        case time = "time"
    }
    
    
    
    init(from decoder: Decoder) throws {
        let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
        if trackContainer.contains(.price){
            price = try trackContainer.decode(Double.self, forKey: .price)
        }else{
            price = 0
        }
        if trackContainer.contains(.ask) {
            ask = try trackContainer.decode(Double.self, forKey: .ask)
        } else {
            ask = 0
        }
        if trackContainer.contains(.askVolume) {
            askVolume = try trackContainer.decode(Double.self, forKey: .askVolume)
        } else {
            askVolume = 0
        }
        if trackContainer.contains(.bid) {
            bid = try trackContainer.decode(Double.self, forKey: .bid)
        } else {
            bid = 0
        }
        if trackContainer.contains(.bidVolume) {
            bidVolume = try trackContainer.decode(Double.self, forKey: .bidVolume)
        } else {
            bidVolume = 0
        }
        if trackContainer.contains(.volume) {
            volume = try trackContainer.decode(Double.self, forKey: .volume)
        } else {
            volume = 0
        }
        if trackContainer.contains(.time) {
            time = try trackContainer.decode(String.self, forKey: .time)
        }
        else {
            time = ""
        }
    }
    
    
}


// MARK: - TradingPairElement
struct TradingPair {
    var id: Int
    var name: String
    var quoteAsset: String
    var baseAsset: String
}

extension TradingPair: Decodable {
    
    enum TrackCodingKeys: String, CodingKey {
        case id = "id"
        case name = "name"
        case quoteAsset = "quoteAsset"
        case baseAsset = "baseAsset"
       
    }
    init(from decoder: Decoder) throws {
        let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
        if trackContainer.contains(.id){
            id = try trackContainer.decode(Int.self, forKey: .id)
        }else{
            id = 0
        }
        if trackContainer.contains(.name) {
            name = try trackContainer.decode(String.self, forKey: .name)
        } else {
            name = ""
        }
        if trackContainer.contains(.quoteAsset) {
            quoteAsset = try trackContainer.decode(String.self, forKey: .quoteAsset)
        } else {
            quoteAsset = ""
        }
        if trackContainer.contains(.baseAsset) {
            baseAsset = try trackContainer.decode(String.self, forKey: .baseAsset)
        } else {
            baseAsset = ""
        }
    }
}

API .GET 请求使用 Alamofire :


class ServerCommunicator {
    
    static func getAssets() -> Promise<[Assets]> {
        let decoder = JSONDecoder()
        return Promise { seal in
            AF.request(API.assets, method: .get, parameters: .none, headers: .none).responseDecodable(of: [Assets].self, decoder: decoder) { response in
                switch response.result {
                    case .success(let assets):
                        return seal.fulfill(assets)
                    case .failure(let error):
                        return seal.reject(error)
                }
            }
        }
    }
    
    static func getPairs() -> Promise<[TradingPair]> {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .useDefaultKeys
        return Promise { seal in
            AF.request(API.tradingPairs, method: .get, parameters: .none, headers: .none).responseDecodable(of: [TradingPair].self, decoder: decoder) { response in
                switch response.result {
                    case .success(let pairs):
                        return seal.fulfill(pairs)
                    case .failure(let error):
                        return seal.reject(error)
                }
            }
        }
    }
    
    static func getPair(with pairName: String?) -> Promise<TickerByPair> {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .useDefaultKeys
        return Promise { seal in
            AF.request(API.getPairByTicker(pairName: pairName!), method: .get, parameters: .none, headers: .none).responseDecodable(of: TickerByPair.self, decoder: decoder) { response in
                switch response.result {
                    case .success(let ticker):
                        return seal.fulfill(ticker)
                    case .failure(let error):
                        return seal.reject(error)
                }
            }
        }
    }
}

而我的json response格式也如下:

[
  {
    "name": "ETH-KRW",
    "baseAsset": "ETH",
    "quoteAsset": "KRW"
  }, {
    "name": "BTC-KRW",
    "baseAsset": "BTC",
    "quoteAsset": "KRW"
  }, {
    "name": "BCH-KRW",
    "baseAsset": "BCH",
    "quoteAsset": "KRW"
  }
]

-------------

{
  "price": 10194500,
  "ask": 10195000,
  "bid": 10184500,
  "volume": 1752.05558316,
  "time": "2018-03-14T03:50:41.184Z"
}

如果你的 API 应该是 return 数组,而不是 return 字典,你可以尝试 re-implementing 方便的 responseDecodable(of:decoder:) Alamofire 的功能。

使用 responseData(queue:completionHandler:) 并尝试将您的响应数据解码为数组,如果失败,请重试为对象类型。

例如,您的 getPairs() 函数可能是这样的:

static func getPairs() -> Promise<[TradingPair]> {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .useDefaultKeys
    return Promise { seal in
        AF.request(API.tradingPairs, method: .get, parameters: .none, headers: .none).responseData { response in
            switch response.result {
            case .success(let data):
                do {
                    let pairs = try decoder.decode([TradingPair].self, from: data)
                    return seal.fulfill(pairs)
                } catch DecodingError.typeMismatch {
                    do {
                        let pair = try decoder.decode(TradingPair.self, from: data)
                        return seal.fulfill([pair])
                    } catch {
                        return seal.reject(error)
                    }
                } catch {
                    return seal.reject(error)
                }
            case .failure(let error):
                return seal.reject(error)
            }
        }
    }
}

或者更一般地,像这样扩展 DataRequest

extension DataRequest {
    @discardableResult func responseArray<T: Decodable>(
        of type: T.Type,
        decoder: DataDecoder,
        completionHandler: @escaping (AFDataResponse<[T]>) -> Void
    ) -> Self {
        responseData { response in
            switch response.result {
            case .success(let data):
                do {
                    let array = try decoder.decode([T].self, from: data)
                    let dataResponse = self.dataResponse(from: response, result: .success(array))
                    completionHandler(dataResponse)
                } catch DecodingError.typeMismatch {
                    do {
                        let object = try decoder.decode(T.self, from: data)
                        let dataResponse = self.dataResponse(from: response, result: .success([object]))
                        completionHandler(dataResponse)
                    } catch {
                        let dataResponse: AFDataResponse<[T]> = self.dataResponse(
                            from: response,
                            result: .failure(.responseSerializationFailed(reason: .decodingFailed(error: error)))
                        )
                        completionHandler(dataResponse)
                    }
                } catch {
                    let dataResponse: AFDataResponse<[T]> = self.dataResponse(
                        from: response,
                        result: .failure(.responseSerializationFailed(reason: .decodingFailed(error: error)))
                    )
                    completionHandler(dataResponse)
                }
            case .failure(let error):
                let dataResponse: AFDataResponse<[T]> = self.dataResponse(from: response, result: .failure(error))
                completionHandler(dataResponse)
            }
        }
    }
    
    private func dataResponse<T: Decodable>(from response: AFDataResponse<Data>, result: Result<[T], AFError>) -> AFDataResponse<[T]> {
        return .init(
            request: response.request,
            response: response.response,
            data: response.data,
            metrics: response.metrics,
            serializationDuration: response.serializationDuration,
            result: result
        )
    }
}

然后你的getPairs()函数会简单得多:

static func getPairs() -> Promise<[TradingPair]> {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .useDefaultKeys
    return Promise { seal in
        AF.request(API.tradingPairs, method: .get, parameters: .none, headers: .none).responseArray(of: TradingPair.self, decoder: decoder) { response in
            switch response.result {
            case .success(let pairs):
                return seal.fulfill(pairs)
            case .failure(let error):
                return seal.reject(error)
            }
        }
    }
}