第九章 Combine的多订阅操作符

1,671 阅读5分钟

Combine 使我们能够通过操作符(例如 share() 和 multicast() 及其可连接的发布者)有效地管理我们的资源,这使我们能够在必要时提高我们的应用程序性能。

Combine 是围绕结构设计的,所以它是值类型,在传递时,系统都会为值类型创建一个副本,以便它可以传递值而没有副作用。

比如下面的代码:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    .publisher
	.print()
   
numbers
	.print("_sink 1_")
    .sink(receiveValue: { _ in })
    .store(in: &cancellables)
numbers
    .print("_sink 2_")
    .sink(receiveValue: { _ in })
    .store(in: &cancellables)

我们创建了一个序列的Publisher,发布从1到10的整数。然后我们创建了两个订阅,都订阅到这个publisher。运行playground,得到结果:

receive subscription: ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])		`第一个订阅`
_sink 1_: receive subscription: (Print)
_sink 1_: request unlimited
request unlimited
receive value: (1)
_sink 1_: receive value: (1)
receive value: (2)
_sink 1_: receive value: (2)
receive value: (3)
_sink 1_: receive value: (3)
receive value: (4)
_sink 1_: receive value: (4)
receive value: (5)
_sink 1_: receive value: (5)
receive value: (6)
_sink 1_: receive value: (6)
receive value: (7)
_sink 1_: receive value: (7)
receive value: (8)
_sink 1_: receive value: (8)
receive value: (9)
_sink 1_: receive value: (9)
receive value: (10)
_sink 1_: receive value: (10)
receive finished
_sink 1_: receive finished
receive subscription: ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])			`第二个订阅`
_sink 2_: receive subscription: (Print)
_sink 2_: request unlimited
request unlimited
receive value: (1)
_sink 2_: receive value: (1)
receive value: (2)
_sink 2_: receive value: (2)
receive value: (3)
_sink 2_: receive value: (3)
receive value: (4)
_sink 2_: receive value: (4)
receive value: (5)
_sink 2_: receive value: (5)
receive value: (6)
_sink 2_: receive value: (6)
receive value: (7)
_sink 2_: receive value: (7)
receive value: (8)
_sink 2_: receive value: (8)
receive value: (9)
_sink 2_: receive value: (9)
receive value: (10)
_sink 2_: receive value: (10)
receive finished
_sink 2_: receive finished

numbers这个publisher其实是被拷贝了一次,用于第二次的订阅。所以说创建多个订阅者时,将创建发布者的副本。

如果在网络请求中有类似的情况,比如一个网络请求的publisher,需要被多个订阅者所订阅,使用这种复制的方式会对系统性能造成影响。这时我们可以使用Combine提供的share操作符来实现。 这个操作符是class类型,所以使用时我们使用的是引用类型而不是值类型。也就避免了拷贝的情况。

share

与多个订阅者共享上游发布者的输出。

它的返回值为:一个类实例,它将从其上游接收到的元素共享给多个订阅者。

此操作符返回的发布者支持多个订阅者,所有订阅者都从上游发布者接收未更改的元素和完成状态。 提示:Publishers.Share 实际上是 Publishers.Multicast 和 PassthroughSubject 发布者的组合,带有隐式 autoconnect()

示例代码:

// Data Model
struct Post: Decodable {
    
    var userId: Int
    var id: Int
    var title: String
    var body: String
    
    
    init(userId: Int = 0, id: Int = 0, title:String = "", body: String = "") {
        self.userId = userId
        self.id = id
        self.title = title
        self.body = body
    }
}

let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
let posts = URLSession.shared.dataTaskPublisher(for: url)
    .map { $0.data }
    .decode(type: Post.self, decoder: JSONDecoder())
    .replaceError(with: Post())
    .print()
    .share()
posts
    .sink(receiveValue: {
        print("subscription1 value: \($0)") })
    .store(in: &cancellables)
posts
    .sink(receiveValue: {
        print("subscription2 value: \($0)") })
    .store(in: &cancellables)


在示例中,我们对https://jsonplaceholder.typicode.com/posts/1进行网络请求,声明posts为网络请求的Publisher,然后我们对其进行了两次订阅。

运行playground,得到结果:

`receive subscription: (ReplaceError)`
request unlimited
receive value: (Post(userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"))
subscription1 value: Post(userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto")
subscription2 value: Post(userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto")
receive finished

可以看到,我们实际只打印出一次订阅的过程(上面结果中红色部分)。两个订阅者都收到了同样的数据,因为他们的上游是同一个数据源,这个数据源对他们来说是共享的。

multicast

当您有多个下游订阅者,且希望上游发布者每个事件只处理一个 receive(_:) 时,可以使用multicast

multicast有两种类型:multicast(_:)multicast(subject:)

multicast(_:)

提供一个闭包来创建一个Subject,用于向订阅者提供元素。

multicast(_:)会产生一个发布者,为每个订阅者创建一个单独的Subject multicast(_:)是一个ConnectablePublisher,所以需要手动调用connect来开始运行

我们改写本章第一个demo:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    .publisher
    .print()
    .multicast { PassthroughSubject<Int, Never>() }
numbers
    .print("_sink 1_")
    .sink(receiveValue: { _ in })
    .store(in: &cancellables)
numbers
    .print("_sink 2_")
    .sink(receiveValue: { _ in })
    .store(in: &cancellables)

numbers.connect()

我们增加了multicast操作符,并在闭包中设置了一个PassthroughSubject的Subject 运行playground,得到结果:

_sink 1_: receive subscription: (Multicast)
_sink 1_: request unlimited
_sink 2_: receive subscription: (Multicast)
_sink 2_: request unlimited
`receive subscription: ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])`
request unlimited
receive value: (1)
_sink 2_: receive value: (1)
_sink 1_: receive value: (1)
receive value: (2)
_sink 2_: receive value: (2)
_sink 1_: receive value: (2)
receive value: (3)
_sink 2_: receive value: (3)
_sink 1_: receive value: (3)
receive value: (4)
_sink 2_: receive value: (4)
_sink 1_: receive value: (4)
receive value: (5)
_sink 2_: receive value: (5)
_sink 1_: receive value: (5)
receive value: (6)
_sink 2_: receive value: (6)
_sink 1_: receive value: (6)
receive value: (7)
_sink 2_: receive value: (7)
_sink 1_: receive value: (7)
receive value: (8)
_sink 2_: receive value: (8)
_sink 1_: receive value: (8)
receive value: (9)
_sink 2_: receive value: (9)
_sink 1_: receive value: (9)
receive value: (10)
_sink 2_: receive value: (10)
_sink 1_: receive value: (10)
receive finished
_sink 2_: receive finished
_sink 1_: receive finished

可以看到,对于使用过multicast的publisher,只接收到了一次订阅。也就是我们介绍multicast所说的,** 希望上游发布者每个事件只处理一个 receive(_:) 。**

multicast(subject:)

此方法生成一个发布者,该发布者在所有下游订阅者之间共享所提供的主题。

回顾刚刚提到的multicast(_:):

multicast(_:)会产生一个发布者,为每个订阅者创建一个单独的Subject

multicast(subject:)在所有下游订阅者之间共享提供的主题。同样multicast(subject:)也是一个ConnectablePublisher,也需要手动调用connect来开始运行。

在实际操作中,两个multicast没有什么不同,在上一个例子中将

.multicast { PassthroughSubject<Int, Never>() }

改写为

.multicast(subject: PassthroughSubject<Int, Never>())

结果是一样的。