假设我有一个简单的链,它从一个 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
}
Run Code Online (Sandbox Code Playgroud)
此代码产生此错误,因为我返回的是 Error 而不是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>'
Run Code Online (Sandbox Code Playgroud)
我知道要解决这个问题,我需要在.decode.
.mapError{error in
APIManagerError.error("Decode Fail")
}
Run Code Online (Sandbox Code Playgroud)
但我无法真正理解在“aka”部分之前的错误消息报告了什么,而是非常清楚
你如何阅读错误Publishers.Decode<Upstream, Output, Coder>.Failure?具体是什么意思.Failure?我Failure在哪里可以找到Swift 文档?
由于我们正在谈论两种不同类型的错误让这里表示一个ç ompilation é RROR为CE和Failure的Publisher(符合Swift.Error)为PF(P ublisher ˚F ailure)。
您的问题是关于CE消息的解释。
Cannot convert return expression of type
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'
Run Code Online (Sandbox Code Playgroud)
写出您的实现的结果返回类型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调用的结果,您的上游发布者最初是 类型,然后您将其转换为您调用的每个 Combine 运算符:map然后是decode。导致发布者Publishers.Decode。并且Failure类型不能被正确地“解符号化”(我缺乏正确的编译器知识来使用正确的术语)。
您使用哪个 Xcode 版本?在新的诊断架构也许能够表现出更好的错误消息。这实际上是我后来在回复中使用的原因.assertMapError(is: DecodingError.self)
您的代码mapError完成了这项工作,但它完全丢弃了有关实际错误的信息。所以我不会那样做。至少打印(记录)错误。但我仍然会做类似的事情:
直觉上,我们至少有两种不同类型的错误,要么是networking解码,要么是解码。但可能更多...
Cannot convert return expression of type
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'
Run Code Online (Sandbox Code Playgroud)
您可能需要告诉 combine 错误类型确实是DecodingError,因此我已经声明了一些fatalError对此信息有用的宏。它与Combine 有点相似setFailureType(但仅在上游发布者具有Failuretype时才有效Never,因此我们不能在这里使用它)。
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)
}
}
Run Code Online (Sandbox Code Playgroud)
然后在 上创建一个方便的方法Publisher,类似于setFailureType:
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
}
Run Code Online (Sandbox Code Playgroud)
我冒昧地在你的例子中发现了更多的错误。断言例如服务器以非故障 HTTP 状态代码等进行响应。
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()
}
}
Run Code Online (Sandbox Code Playgroud)
HTTPError如果我们的错误类型是Equatable,这确实非常有利,它使编写单元测试变得更加容易。要么我们走这Equatable条路线,要么我们可以做一些反射魔法。我将介绍这两种解决方案,但 Equatable 解决方案肯定更强大。
为了使HTTPError符合Equatable我们只需要手动使DecodingError相等。我用这个代码完成了这个:
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()
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这也必须使DecodingError.ContextEquatable成为可能。
然后你可以声明这些 XCTest 助手:
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
}
}
Run Code Online (Sandbox Code Playgroud)
或者您可以在这里查看我的Gist,它根本不使用 Equatable,但可以“比较”任何不符合 Equatable 的枚举错误。
CombineExpectation现在,您可以与您一起编写组合代码的单元测试并更轻松地比较错误!
| 归档时间: |
|
| 查看次数: |
1074 次 |
| 最近记录: |