如何让发布者只发送给 Combine 中的最后一个订阅者
How to have a publisher emit only to the last subscriber in Combine
有没有办法让发布者只向最新的subscriber/observer发送一个值?
一个例子是;可以由多个观察者订阅的管理器 class。当事件发生时,我希望只观察到最新的订阅者。据我所知,发布者无法跟踪其订阅者,但我对 Combine 和反应式编程的了解有限,所以我不确定这是否可能。
你是对的。不幸的是,没有办法 list/track 发布者的订阅者。要解决您的问题,您必须实施自定义发布者。这里有两种可能性。您可以使用 Publisher
协议实现自定义发布者,但 Apple 建议不要这样做 (see here),或者您可以按照 Apple 的建议创建具有现有类型的自定义发布者。我已经为第二个选项准备了一个例子。
道理很简单。我们创建一个内部带有 PassthroughSubject
的发布者(它也可以是 CurrentValueSubject
)。然后我们实现 PassthroughSubject
的典型方法,并使用它们覆盖 class 中 PassthroughSubject
的相同方法。在 sink
方法中,我们存储所有返回的订阅,但在向 Set
添加新订阅之前,我们遍历所有已缓存的订阅并取消它们。这样我们就实现了只有最后一个订阅有效的目标。
// The subscriptions will be cached in the publisher.
// To avoid strong references, I use the WeakBox recommendation from the Swift forum.
struct WeakBox<T: AnyObject & Hashable>: Hashable {
weak var item: T?
func hash(into hasher: inout Hasher) {
hasher.combine(item)
}
}
class MyPublisher<T, E: Error> {
private let subject = PassthroughSubject<T, E>()
private var subscriptions = Set<WeakBox<AnyCancellable>>()
deinit {
subscriptions.removeAll()
}
public func send(_ input: T) {
subject.send(input)
}
public func send(completion: Subscribers.Completion<E>) {
subject.send(completion: completion)
}
public func sink(receiveCompletion receivedCompletion: @escaping (Subscribers.Completion<E>) -> Void, receiveValue receivedValue: @escaping (T) -> Void) -> AnyCancellable {
let subscription = subject
.sink(receiveCompletion: { completion in
receivedCompletion(completion)
}, receiveValue: { value in
receivedValue(value)
})
// Cancel previous subscriptions.
subscriptions.forEach { [=10=].item?.cancel() }
// Add new subscription.
subscriptions.insert(WeakBox(item: subscription))
return subscription
}
}
我在 Playground 中测试了 class 如下。
let publisher = MyPublisher<Int, Never>()
let firstSubscription = publisher
.sink(receiveCompletion: { completion in
print("1st subscription completion \(completion)")
}, receiveValue: { value in
print("1st subscription value \(value)")
})
let secondSubscription = publisher
.sink(receiveCompletion: { completion in
print("2st subscription completion \(completion)")
}, receiveValue: { value in
print("2st subscription value \(value)")
})
let thirdSubscription = publisher
.sink(receiveCompletion: { completion in
print("3st subscription completion \(completion)")
}, receiveValue: { value in
print("3st subscription value \(value)")
})
publisher.send(123)
控制台输出:
3st subscription value 123
如果您注释掉 subscriptions.forEach { [=21=].cancel() }
行,您将得到:
3st subscription value 123
1st subscription value 123
2st subscription value 123
希望能帮到你。
有没有办法让发布者只向最新的subscriber/observer发送一个值?
一个例子是;可以由多个观察者订阅的管理器 class。当事件发生时,我希望只观察到最新的订阅者。据我所知,发布者无法跟踪其订阅者,但我对 Combine 和反应式编程的了解有限,所以我不确定这是否可能。
你是对的。不幸的是,没有办法 list/track 发布者的订阅者。要解决您的问题,您必须实施自定义发布者。这里有两种可能性。您可以使用 Publisher
协议实现自定义发布者,但 Apple 建议不要这样做 (see here),或者您可以按照 Apple 的建议创建具有现有类型的自定义发布者。我已经为第二个选项准备了一个例子。
道理很简单。我们创建一个内部带有 PassthroughSubject
的发布者(它也可以是 CurrentValueSubject
)。然后我们实现 PassthroughSubject
的典型方法,并使用它们覆盖 class 中 PassthroughSubject
的相同方法。在 sink
方法中,我们存储所有返回的订阅,但在向 Set
添加新订阅之前,我们遍历所有已缓存的订阅并取消它们。这样我们就实现了只有最后一个订阅有效的目标。
// The subscriptions will be cached in the publisher.
// To avoid strong references, I use the WeakBox recommendation from the Swift forum.
struct WeakBox<T: AnyObject & Hashable>: Hashable {
weak var item: T?
func hash(into hasher: inout Hasher) {
hasher.combine(item)
}
}
class MyPublisher<T, E: Error> {
private let subject = PassthroughSubject<T, E>()
private var subscriptions = Set<WeakBox<AnyCancellable>>()
deinit {
subscriptions.removeAll()
}
public func send(_ input: T) {
subject.send(input)
}
public func send(completion: Subscribers.Completion<E>) {
subject.send(completion: completion)
}
public func sink(receiveCompletion receivedCompletion: @escaping (Subscribers.Completion<E>) -> Void, receiveValue receivedValue: @escaping (T) -> Void) -> AnyCancellable {
let subscription = subject
.sink(receiveCompletion: { completion in
receivedCompletion(completion)
}, receiveValue: { value in
receivedValue(value)
})
// Cancel previous subscriptions.
subscriptions.forEach { [=10=].item?.cancel() }
// Add new subscription.
subscriptions.insert(WeakBox(item: subscription))
return subscription
}
}
我在 Playground 中测试了 class 如下。
let publisher = MyPublisher<Int, Never>()
let firstSubscription = publisher
.sink(receiveCompletion: { completion in
print("1st subscription completion \(completion)")
}, receiveValue: { value in
print("1st subscription value \(value)")
})
let secondSubscription = publisher
.sink(receiveCompletion: { completion in
print("2st subscription completion \(completion)")
}, receiveValue: { value in
print("2st subscription value \(value)")
})
let thirdSubscription = publisher
.sink(receiveCompletion: { completion in
print("3st subscription completion \(completion)")
}, receiveValue: { value in
print("3st subscription value \(value)")
})
publisher.send(123)
控制台输出:
3st subscription value 123
如果您注释掉 subscriptions.forEach { [=21=].cancel() }
行,您将得到:
3st subscription value 123
1st subscription value 123
2st subscription value 123
希望能帮到你。