您需要使用flatMap,而不是tryMap,围绕dataTaskPublisher(for:)。
查看类型。从这个开始:
let p0 = OAuthSession.current.makeAuthenticatedRequest(request: request)
按住 Option 键单击 p0 以查看其推断类型。它是AnyPublisher<URLRequest, OAuthError>,因为这就是makeAuthenticatedRequest(request:) 被声明要返回的内容。
现在添加:
let p1 = p0.tryMap(URLSession.shared.dataTaskPublisher(for:))
按住 Option 键单击 p1 以查看其推导类型 Publishers.TryMap<AnyPublisher<URLRequest, OAuthError>, URLSession.DataTaskPublisher>。哎呀,这有点难以理解。使用eraseToAnyPublisher 进行简化:
let p1 = p0
.tryMap(URLSession.shared.dataTaskPublisher(for:))
.eraseToAnyPublisher()
现在p1 的推断类型是AnyPublisher<URLSession.DataTaskPublisher, Error>。里面仍然有一个有点神秘的类型URLSession.DataTaskPublisher,所以让我们也删除它:
let p1 = p0.tryMap {
URLSession.shared.dataTaskPublisher(for: $0)
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
现在Xcode可以告诉我们p1的推断类型是AnyPublisher<AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>, OAuthError>。让我重新格式化以提高可读性:
AnyPublisher<
AnyPublisher<
URLSession.DataTaskPublisher.Output,
URLSession.DataTaskPublisher.Failure>,
OAuthError>
这是一个发布者发布URLSession.DataTaskPublisher.Output的发布者。
这不是您所期望的,这也是您的第二个 tryMap 失败的原因。您以为您正在创建 URLSession.DataTaskPublisher.Output 的发布者(这是元组 (data: Data, response: URLResponse) 的 typealias),这就是您的第二个 tryMap 想要的输入。但 Combine 认为你的第二个 tryMap 的输入应该是 URLSession.DataTaskPublisher。
当您看到这种嵌套,以及发布发布者的发布者时,这意味着您可能需要使用flatMap 而不是map(或tryMap)。让我们这样做:
let p1 = p0.flatMap {
// ^^^^^^^ flatMap instead of tryMap
URLSession.shared.dataTaskPublisher(for: $0)
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
现在我们得到一个编译时错误:
? 实例方法 'flatMap(maxPublishers:_:)' 要求类型 'OAuthError' 和 'URLSession.DataTaskPublisher.Failure'(又名 'URLError')是等价的
问题是由于外部发布者的失败类型为OAuthError,而内部发布者的失败类型为URLError,Combine 无法展平嵌套。如果它们具有相同的故障类型,Combine 只能将它们展平。我们可以通过将两种故障类型转换为通用的Error 类型来解决这个问题:
let p1 = p0
.mapError { $0 as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: $0)
.mapError { $0 as Error }
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
这样编译,Xcode 告诉我们推导的类型是AnyPublisher<URLSession.DataTaskPublisher.Output, Error>,这就是我们想要的。我们可以添加您的下一个 tryMap,但我们只需使用 map,因为正文不会抛出任何错误:
let p2 = p1.map { $0.data }.eraseToAnyPublisher()
Xcode 告诉我们p2 是一个AnyPublisher<Data, Error>,所以我们可以链接一个decode 修饰符。
现在我们已经理顺了类型,我们可以摆脱所有类型的橡皮擦并将它们放在一起:
OAuthSession.current.makeAuthenticatedRequest(request: request)
.mapError { $0 as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: $0)
.mapError { $0 as Error } }
.map { $0.data }
.decode(type: A.self, decoder: decoder)