gpt4 book ai didi

Swift 组合链错误中的 Swift `Failure` 关键字含义

转载 作者:行者123 更新时间:2023-12-04 00:22:51 26 4
gpt4 key购买 nike

假设我有一个简单的链,它从一个 HTTP 请求为 <T, APIManagerError> 创建一个发布者。

   func run<T:Decodable>(request:URLRequest)->AnyPublisher<T, APIManagerError>{
return URLSession.shared.dataTaskPublisher(for: request)
.map{$0.data}
.decode(type: T.self, decoder: JSONDecoder())
.eraseToAnyPublisher()// it should run mapError before this point
}

此代码产生此错误,因为我返回的是错误而不是 APIManagerError .

Cannot convert return expression of type 
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'
(aka 'AnyPublisher<T, Error>')
to return type 'AnyPublisher<T, RestManagerError>'

我知道要解决此问题,我需要在 .decode 之后添加 mapError .

.mapError{error in 
APIManagerError.error("Decode Fail")
}

但我无法真正理解“aka”部分之前的错误消息所报告的内容,而是非常清楚

您如何阅读错误 Publishers.Decode<Upstream, Output, Coder>.Failure ?具体是什么意思.Failure部分?我在哪里可以找到 Failure在 Swift 文档中?

最佳答案

由于我们在这里讨论两种不同类型的错误,我们将C编译E错误表示为CEFailurePublisher (符合 Swift.Error )为 PF (P发布者F失败)。

您的问题是关于 CE 的解释。消息。

Cannot convert return expression of type 
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'

写出 func run 实现的结果返回类型- 没有 mapError称呼。编译器确认您对 eraseToAnyPublisher() 的调用在函数的末尾,还有你的通用 Output类型 T .这涵盖了Cannot convert return expression of type 'AnyPublisher<T, .至于Publishers.Decode<Upstream, Output, Coder>.Failure>'输出 Failure 的派生类型.这在某种程度上是派生的 Failure 的象征性分解。类型。您的上游发布者最初的类型为 URLSession.DataTaskPublisher ,由于您的 URLSession.shared.dataTaskPublisher调用,然后使用您调用的每个组合运算符对其进行转换:map然后 decode .导致发布者 Publishers.Decode .和 Failure type of 无法正确“去符号化”(我缺乏正确的编译器知识来使用正确的术语)。

您使用哪个 Xcode 版本? New Diagnostic Architecture可能能够显示更好的错误消息。这实际上是我后来在回复中使用 .assertMapError(is: DecodingError.self) 的原因。

您在 mapError 中的代码完成了这项工作,但它完全抛弃了有关实际错误的信息。所以我不会那样做。至少打印(记录)错误。但我仍然会做类似的事情:

声明自定义错误类型

直观地说,我们至少有两种不同的错误,networking或解码。但可能更多......

public enum HTTPError: Swift.Error {
indirect case networkingError(NetworkingError)
indirect case decodingError(DecodingError)
}

public extension HTTPError {
enum NetworkingError: Swift.Error {
case urlError(URLError)
case invalidServerResponse(URLResponse)
case invalidServerStatusCode(Int)
}
}

您可能需要告诉 combine 错误类型确实是 DecodingError ,因此我声明了一些 fatalError对这些信息有用的宏。与Combine 的setFailureType 有点相似。 (但仅当上游发布者有 Failure 类型 Never 时才有效,因此我们不能在这里使用它。

castOrKill

func typeErasureExpected<T>(
instance incorrectTypeOfThisInstance: Any,
toBe expectedType: T.Type,
_ file: String = #file,
_ line: Int = #line
) -> Never {
let incorrectTypeString = String(describing: Mirror(reflecting: incorrectTypeOfThisInstance).subjectType)
fatalError(
"Incorrect implementation: Expected variable '\(incorrectTypeOfThisInstance)' (type: '\(incorrectTypeString)') to be of type `\(expectedType)`",
file, line
)
}

func castOrKill<T>(
instance anyInstance: Any,
toType: T.Type,
_ file: String = #file,
_ line: Int = #line
) -> T {
guard let instance = anyInstance as? T else {
typeErasureExpected(instance: anyInstance, toBe: T.self, file, line)
}
return instance
}

然后在 Publisher 上创建一个便捷方法, 类似于 setFailureType :

extension Publisher {
func assertMapError<NewFailure>(is newFailureType: NewFailure.Type) -> AnyPublisher<Output, NewFailure> where NewFailure: Swift.Error {
return self.mapError { castOrKill(instance: $0, toType: NewFailure.self) }.eraseToAnyPublisher()
}
}

用法:

我冒昧地在您的示例中发现了更多错误。断言例如服务器以非失败 HTTP 状态码等响应。

func run<Model>(request: URLRequest) -> AnyPublisher<Model, HTTPError> where Model: Decodable {
URLSession.shared
.dataTaskPublisher(for: request)
.mapError { HTTPError.NetworkingError.urlError($0) }
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse else {
throw HTTPError.NetworkingError.invalidServerResponse(response)
}
guard case 200...299 = httpResponse.statusCode else {
throw HTTPError.NetworkingError.invalidServerStatusCode(httpResponse.statusCode)
}
return data
}
.decode(type: Model.self, decoder: JSONDecoder())

// It's unfortunate that Combine does not pick up that failure type is `DecodingError`
// thus we have to manually tell the Publisher this.
.assertMapError(is: DecodingError.self)
.mapError { HTTPError.decodingError($0) }
.eraseToAnyPublisher()
}

奖励 - HTTPError 的相等性检查

如果我们的错误类型是 Equatable 确实是非常有利的。 ,它使编写单元测试变得更加容易。要么我们去Equatable路线,或者我们可以做一些反射魔法。我将介绍这两种解决方案,但 Equatable 解决方案肯定更强大。

平等的

为了制作HTTPError符合 Equatable我们只需要手动制作DecodingError等价的。我已经用这段代码完成了这个:


extension DecodingError: Equatable {

public static func == (lhs: DecodingError, rhs: DecodingError) -> Bool {

switch (lhs, rhs) {

/// `typeMismatch` is an indication that a value of the given type could not
/// be decoded because it did not match the type of what was found in the
/// encoded payload. As associated values, this case contains the attempted
/// type and context for debugging.
case (
.typeMismatch(let lhsType, let lhsContext),
.typeMismatch(let rhsType, let rhsContext)):
return lhsType == rhsType && lhsContext == rhsContext

/// `valueNotFound` is an indication that a non-optional value of the given
/// type was expected, but a null value was found. As associated values,
/// this case contains the attempted type and context for debugging.
case (
.valueNotFound(let lhsType, let lhsContext),
.valueNotFound(let rhsType, let rhsContext)):
return lhsType == rhsType && lhsContext == rhsContext

/// `keyNotFound` is an indication that a keyed decoding container was asked
/// for an entry for the given key, but did not contain one. As associated values,
/// this case contains the attempted key and context for debugging.
case (
.keyNotFound(let lhsKey, let lhsContext),
.keyNotFound(let rhsKey, let rhsContext)):
return lhsKey.stringValue == rhsKey.stringValue && lhsContext == rhsContext

/// `dataCorrupted` is an indication that the data is corrupted or otherwise
/// invalid. As an associated value, this case contains the context for debugging.
case (
.dataCorrupted(let lhsContext),
.dataCorrupted(let rhsContext)):
return lhsContext == rhsContext

default: return false
}
}
}

extension DecodingError.Context: Equatable {
public static func == (lhs: DecodingError.Context, rhs: DecodingError.Context) -> Bool {
return lhs.debugDescription == rhs.debugDescription
}
}

如您所见,这也必须使 DecodingError.Context平等的。

然后你可以声明这些 XCTest 助手:

    func XCTAssertThrowsSpecificError<ReturnValue, ExpectedError>(
file: StaticString = #file,
line: UInt = #line,
_ codeThatThrows: @autoclosure () throws -> ReturnValue,
_ error: ExpectedError,
_ message: String = ""
) where ExpectedError: Swift.Error & Equatable {

XCTAssertThrowsError(try codeThatThrows(), message, file: file, line: line) { someError in
guard let expectedErrorType = someError as? ExpectedError else {
XCTFail("Expected code to throw error of type: <\(ExpectedError.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
return
}
XCTAssertEqual(expectedErrorType, error, line: line)
}
}

func XCTAssertThrowsSpecificError<ExpectedError>(
_ codeThatThrows: @autoclosure () throws -> Void,
_ error: ExpectedError,
_ message: String = ""
) where ExpectedError: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message) { someError in
guard let expectedErrorType = someError as? ExpectedError else {
XCTFail("Expected code to throw error of type: <\(ExpectedError.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
return
}
XCTAssertEqual(expectedErrorType, error)
}
}

func XCTAssertThrowsSpecificErrorType<Error>(
_ codeThatThrows: @autoclosure () throws -> Void,
_ errorType: Error.Type,
_ message: String = ""
) where Error: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message) { someError in
XCTAssertTrue(someError is Error, "Expected code to throw error of type: <\(Error.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
}
}

反射魔法

或者你可以看看我的Gist here它根本不使用 Equatable,但可以“比较”任何不符合 Equatable 的枚举错误。

用法

CombineExpectation 一起您现在可以为您的组合代码编写单元测试并更轻松地比较错误!

关于Swift 组合链错误中的 Swift `Failure` 关键字含义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59342986/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com