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

jja*_*tie 12 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
                    // ...
                }
             // ...
             }
        }
Run Code Online (Sandbox Code Playgroud)

我试图让我的 OAuth 框架使用Combine,所以我知道有一个发布者版本的makeAuthenticatedRequest方法,即:

public func makeAuthenticatedRequest(request: URLRequest) -> AnyPublisher<URLRequest, OAuthError>
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用它来替换上面的调用站点,如下所示:

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

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

rob*_*off 30

您需要使用flatMap,不tryMap,各地dataTaskPublisher(for:)

看种类。从这个开始:

let p0 = OAuthSession.current.makeAuthenticatedRequest(request: request)
Run Code Online (Sandbox Code Playgroud)

Option-单击p0以查看其推导类型。它是AnyPublisher<URLRequest, OAuthError>,因为那makeAuthenticatedRequest(request:)是宣布返回的内容。

现在添加这个:

let p1 = p0.tryMap(URLSession.shared.dataTaskPublisher(for:))
Run Code Online (Sandbox Code Playgroud)

Option 键单击p1以查看其推导类型,Publishers.TryMap<AnyPublisher<URLRequest, OAuthError>, URLSession.DataTaskPublisher>。哎呀,这有点难以理解。通过使用简化它eraseToAnyPublisher

let p1 = p0
    .tryMap(URLSession.shared.dataTaskPublisher(for:))
    .eraseToAnyPublisher()
Run Code Online (Sandbox Code Playgroud)

现在推导出的类型p1AnyPublisher<URLSession.DataTaskPublisher, Error>。那仍然有一些神秘的类型URLSession.DataTaskPublisher,所以让我们也删除它:

let p1 = p0.tryMap {
    URLSession.shared.dataTaskPublisher(for: $0)
        .eraseToAnyPublisher() }
    .eraseToAnyPublisher()
Run Code Online (Sandbox Code Playgroud)

现在 Xcode 可以告诉我们推导出的类型p1AnyPublisher<AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>, OAuthError>. 让我重新格式化它以提高可读性:

AnyPublisher<
    AnyPublisher<
        URLSession.DataTaskPublisher.Output, 
        URLSession.DataTaskPublisher.Failure>,
    OAuthError>
Run Code Online (Sandbox Code Playgroud)

它是发布发布者的发布者URLSession.DataTaskPublisher.Output

这不是您所期望的,这就是您的第二个tryMap失败的原因。你以为你正在创建一个URLSession.DataTaskPublisher.Outputtypealias元组的a )的发布者,这就是(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()
Run Code Online (Sandbox Code Playgroud)

现在我们得到一个编译时错误:

实例方法“flatMap(maxPublishers:_:)”要求类型“OAuthError”和“URLSession.DataTaskPublisher.Failure”(又名“URLError”)等价

问题在于,Combine 无法展平嵌套,因为外部发布者的失败类型是OAuthError,而内部发布者的失败类型是URLError。如果它们具有相同的故障类型,Combine 只能将它们展平。我们可以通过将两种故障类型转换为一般Error类型来解决这个问题:

let p1 = p0
    .mapError { $0 as Error }
    .flatMap {
        URLSession.shared.dataTaskPublisher(for: $0)
            .mapError { $0 as Error }
            .eraseToAnyPublisher() }
    .eraseToAnyPublisher()
Run Code Online (Sandbox Code Playgroud)

这会编译,Xcode 告诉我们推导出的类型是AnyPublisher<URLSession.DataTaskPublisher.Output, Error>,这就是我们想要的。我们可以添加您的 next tryMap,但让我们map改为使用,因为主体不会抛出任何错误:

let p2 = p1.map { $0.data }.eraseToAnyPublisher()
Run Code Online (Sandbox Code Playgroud)

Xcode 告诉我们p2是 an 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)
Run Code Online (Sandbox Code Playgroud)