如何使用 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>
类型的发布者
但这不是你要求的。您希望将数据解析为 SuccessfulResult
或 FailedResult
。这也增加了 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)
编辑: 我正在尽力使我的问题更简单, 在这里,我试图获得解决方案的是,我有一个 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>
但这不是你要求的。您希望将数据解析为 SuccessfulResult
或 FailedResult
。这也增加了 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)