合并将一个发布者变成另一个发布者

时间:2019-07-22 20:22:30

标签: ios swift combine

我使用OAuth框架来异步创建经过身份验证的请求,如下所示:

OAuthSession.current.makeAuthenticatedRequest(request: myURLRequest) { (result: Result<URLRequest, OAuthError>) in
            switch result {
            case .success(let request):
                URLSession.shared.dataTask(with: request) { (data, response, error) in
                    // ...
                }
             // ...
             }
        }

我正在尝试使我的OAuth框架使用Combine,所以我知道makeAuthenticatedRequest方法的发布者版本,即:

public func makeAuthenticatedRequest(request: URLRequest) -> AnyPublisher<URLRequest, OAuthError>

我正试图用它来替换上面的呼叫站点,就像这样:

OAuthSession.current.makeAuthenticatedRequestPublisher(request)
    .tryMap(URLSession.shared.dataTaskPublisher(for:))
    .tryMap { (data, _) in data } // Problem is here
    .decode(type: A.self, decoder: decoder)

如上所述,问题在于将发布者的结果变成新的发布者。我该怎么做呢?

1 个答案:

答案 0 :(得分:1)

您需要在flatMap周围使用tryMap,而不是dataTaskPublisher(for:)

查看类型。从此开始:

let p0 = OAuthSession.current.makeAuthenticatedRequest(request: request)

p0上单击鼠标右键以查看其推导类型。它是AnyPublisher<URLRequest, OAuthError>,因为这是声明makeAuthenticatedRequest(request:)返回的内容。

现在添加此:

let p1 = p0.tryMap(URLSession.shared.dataTaskPublisher(for:))

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的发布者(元组typealias的发布者是(data: Data, response: URLResponse)),这就是第二个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告诉我们p2AnyPublisher<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)