如何让发布者只发送给 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

希望能帮到你。