如何使用 Combine Swift 为成功和失败响应实施 API

How to Implement API for both Success and Failure Response with Combine Swift

编辑: 我正在尽力使我的问题更简单, 在这里,我试图获得解决方案的是,我有一个 API,如果我的数据有效,API 将给出正确的响应,为此我需要使用 [ 中的相应结构进行解码=37=].

另外,如果我的数据有误,API 将会失败,并且会产生一个不同结构的错误响应。

使用 combine,我只能解码一个结构类型。

那么如何让我的解码接受任何类型? 泛型是我希望解决的一种方法,但我认为我需要实现的协议是一个限制我使用泛型的问题。

感谢您的尝试。

// 标记:- ResponseStruct 模型

struct ResponseStruct: Codable {
   
}

//标记:-协议

public protocol RequestProtocol{
    associatedtype ResponseOutput 
    func fetchFunction() -> AnyPublisher<ResponseOutput, Error>
}

//标记:- 实现

struct RequestStruct: Codable, RequestProtocol {
    typealias ResponseOutput = ResponseStruct

    
    func fetchFunction() -> AnyPublisher<ResponseOutput, Error>  {
        let networkManager = NetworkManager()
        do {
            return try networkManager.apiCall(url: url, method: .post, body: JSONEncoder().encode(self))
                .decode(type: ResponseStruct.self, decoder: JSONDecoder())
                .eraseToAnyPublisher()
        } catch {
            
        }
    }
}



上面是代码,如果 API 调用有效,这很好,但如果调用失败,我将收到错误响应,那么如何以组合方式解码该结构?我不想为此再写一个电话,我希望能与联合收割机中的失败有关。或者我可以使关联类型(参见协议)通用吗?

请耐心等待。我想我理解这个问题,但我很难将它与您提供的代码对齐。你的 fetchFunction 特别奇怪,我不明白你的协议试图完成什么。

让我从问题陈述开始,探索解决方案。我会做的 step-by-step 所以这将是一个很长的回复。 tl;dr 是最后的游乐场。

I have an API and if my data is valid the API will give the correct response, for which I need to decode with the respective struct in swift.
If my data is wrong the API will fail and it will produce an error response which is a different struct.

所以我们需要两个结构。一为成功,一为失败。我会补上一些:

struct SuccessfulResult : Decodable {
    let interestingText : String
}

struct FailedResult : Decodable {
    let errorCode : Int
    let failureReason : String
}

基于此,对网络的请求可以:

  • Return 成功数据解码为 SuccessfulResult
  • Return 失败数据解码为 FailedResult
  • 因 low-level 错误而失败(例如,网络无法访问)。

让我们为“网络工作正常并为我提供成功数据或失败数据”创建一个类型:

enum NetworkData {
    case success(Data)
    case failure(Data)
}

对于 low-level 个错误,我将使用 Error

对于这些类型,API 请求可以表示为 AnyPublisher<NetworkData, Error>

类型的发布者

但这不是你要求的。您希望将数据解析为 SuccessfulResultFailedResult。这也增加了 JSON 解析失败的可能性,我也将扫过通用 Error.

的地毯

我们需要一个数据类型来表示 NetworkData:

的解析变体
enum ParsedNetworkData {
    case success(SuccessfulResult)
    case failure(FailedResult)
}

这意味着您要求的 real 网络请求类型是 AnyPublisher<ParsedNetworkData,Error>

类型的发布者

我们可以编写一个函数将 Data 承载网络请求 AnyPublisher<NetworkData,Error> 转换为 AnyPublisher<ParsedNetworkData,Error>.

编写该函数的一种方法是:

func transformRawNetworkRequest(_ networkRequest: AnyPublisher<NetworkData,Error>) -> AnyPublisher<ParsedNetworkData, Error> {

    let decoder = JSONDecoder()
    return networkRequest
        .tryMap { networkData -> ParsedNetworkData in
            switch(networkData) {
                case .success(let successData):
                    return ParsedNetworkData.success(try decoder.decode(SuccessfulResult.self, from: successData))
                case .failure(let failureData):
                    return ParsedNetworkData.failure(try decoder.decode(FailedResult.self, from: failureData))
            }
        }
        .eraseToAnyPublisher()
}

为了练习代码,我们可以编写一个函数来创建一个虚假的网络请求,并添加一些代码来进行尝试。将它们放在一个游乐场中,您会得到:

import Foundation
import Combine

struct SuccessfulResult : Decodable {
    let interestingText : String
}

struct FailedResult : Decodable {
    let errorCode : Int
    let failureReason : String
}

enum NetworkData {
    case success(Data)
    case failure(Data)
}

enum ParsedNetworkData {
    case success(SuccessfulResult)
    case failure(FailedResult)
}

func transformRawNetworkRequest(_ networkRequest: AnyPublisher<NetworkData,Error>) -> AnyPublisher<ParsedNetworkData, Error> {

    let decoder = JSONDecoder()
    return networkRequest
        .tryMap { networkData -> ParsedNetworkData in
            switch(networkData) {
                case .success(let successData):
                    return ParsedNetworkData.success(try decoder.decode(SuccessfulResult.self, from: successData))
                case .failure(let failureData):
                    return ParsedNetworkData.failure(try decoder.decode(FailedResult.self, from: failureData))
            }
        }
        .eraseToAnyPublisher()
}

func fakeNetworkRequest(shouldSucceed: Bool) -> AnyPublisher<NetworkData,Error> {
    let successfulBody = """
    { "interestingText" : "This is interesting!" }
    """.data(using: .utf8)!

    let failedBody = """
    {
      "errorCode" : -4242,
      "failureReason" : "Bogus! Stuff went wrong."
    }
    """.data(using: .utf8)!

    return Future<NetworkData,Error> { fulfill in
        let delay = Set(stride(from: 100, to: 600, by: 100)).randomElement()!

        DispatchQueue.global(qos: .background).asyncAfter(
            deadline: .now() + .milliseconds(delay)) {
                if(shouldSucceed) {
                    fulfill(.success(NetworkData.success(successfulBody)))
                } else {
                    fulfill(.success(NetworkData.failure(failedBody)))
                }
        }
    }.eraseToAnyPublisher()
}

var subscriptions = Set<AnyCancellable>()
let successfulRequest = transformRawNetworkRequest(fakeNetworkRequest(shouldSucceed: true))
successfulRequest
    .sink(receiveCompletion:{ debugPrint([=15=]) },
          receiveValue:{ debugPrint("Success Result \([=15=])") })
    .store(in: &subscriptions)

let failedRequest = transformRawNetworkRequest(fakeNetworkRequest(shouldSucceed: false))
failedRequest
    .sink(receiveCompletion:{ debugPrint([=15=]) },
          receiveValue:{ debugPrint("Failure Result \([=15=])") })
    .store(in: &subscriptions)