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(_:)会产生一个发布者,为每个订阅者创建一个单独的Subjectmulticast(_:)是一个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>())
结果是一样的。