Combine 学习笔记(六):Operator 与类型擦除

一、Operator

默认情况下,订阅某个 Publisher,Subscriber 中的 Input 和 Failure 要与 Publisher 的 Output 和 Failure 类型相同。然而实际开发中往往并没有这么理想,此时就要借助 Operator 进行转换。Operator 遵守 Publisher 协议,负责从数据流上游的 Publisher 订阅值,经过转换生成新的 Publisher 发送给下游的 Subscriber。

Publisher,Operator和 Subscriber三者组成了数据流从发布,转换,到订阅的完整链条。

我们来看一下下面这个例子:

import Combine

let subscription = Just(520)
    .map { value -> String in
        return "I love you"
    }.sink { receivedValue in
        print("result: \(receivedValue)")
    }

/* 输出:
 result: I love you
 */

Publisher 发布了一个 Int 型 520,最后订阅时给 Subscriber 的是一个 String 类型的值 I love you,中间经过 map 这个 Operator 进行转换。

二、类型擦除

从上文我们知道,Operator 实际上就是对 Publisher 发送的值进行一定的处理和转换,这样会改变 Publisher 的类型使得其变成多个 Publisher 的嵌套,使得类型的形式变得特别复杂、难以阅读。事实上,对于 Subscriber 来说只需要关心 Publisher 的 Output 和 Failure 两个类型就能顺利订阅,它并不需要具体知道这个 Publisher 是如何得到、如何嵌套的。为了对复杂类型的 Publisher 进行类型擦除,Combine 提供了 eraseToAnyPublisher() 方法来将复杂的 Publisher 转化为对应的通用类型 AnyPublisher。

这点很像 SwiftUI,SwiftUI给出规避复杂类型的方案是在 Swift 5.1中加上 some关键字。读者不妨回顾我写的这篇文章来查看详细的解析过程,这里不再赘述。

类型擦除后的 Publisher 变得简单明了易于理解,在实际开发中经常使用,比如下面这个例子:

import Combine

let p1 = [[1, 2, 3], [4, 5, 6]]
    .publisher
    .flatMap { $0.publisher } // flatMap用于把这个上面的二维数组拉直
    .map { $0 * 2 }

我们此时来看看 p1 的类型是什么东西:

可以看到 Xcode 为我们自动推断了类型,是非常复杂的一大串东西,繁琐且难以阅读。而且这只是一个简单的例子,实际开发中的例子肯定还要复杂。那我们加上类型擦除之后的结果又是什么呢?

import Combine

let p1 = [[1, 2, 3], [4, 5, 6]]
    .publisher
    .flatMap { $0.publisher }
    .map { $0 * 2 }

let p2 = p1.eraseToAnyPublisher()

我们看到 Publisher 的类型发生了改变,虽然这个形式看似也很复杂,但是我们能直观的看出它的 Output 是 Int 类型(第一行末尾),而剩下的其实是这个新 Publisher 的 Error 类型,一般我们不用关心。类型擦除后的 Publisher 变得简单明了易于理解,在实际开发中经常使用。

需要注意的是,类型擦除也同样会擦除一些特殊 Publisher 的特有方法。下面我们以 PassthroughSubject 为例:

import Combine

let subject = PassthroughSubject<Int, Never>()

let publisher = subject.eraseToAnyPublisher() //进行类型擦除

let subscription = publisher.sink(receiveValue: { print($0) })

subject.send(10)

publisher.send(10) // 这里会报错,因为类型擦除之后的AnyPublisher没有send方法

可见原本能够手动发送数据的 PassthroughSubject 在类型擦除后失去了这一特性,这点我们要在实际开发中尤为注意。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注