在RxSwift中,我们可以使用hasObserver
检查*Subject
是否有任何观察者,如何在例如PassthroughSubject
?
答案 0 :(得分:1)
不需要任何时间... Apple并没有通过API提供此功能,实际上,我不建议这样做,因为这就像在ARC-Objective-C之前手动检查retainCount
的值一样在代码中做出一些决定。
无论如何,都是可能的。让我们将其视为实验室练习。希望有人能对此有所帮助。
免责声明:以下代码并未在所有Publisher上进行过测试,因此对于某些实际项目并不安全。这只是方法演示。
因此,由于存在许多类型的发布者,并且它们都是最终发布者和私有发布者,而且可能通过类型擦除器来实现,因此我们需要将通用的东西应用于任何发布者,从而适用于运营商
extension Publisher {
public func countingSubscribers(_ callback: ((Int) -> Void)? = nil)
-> Publishers.SubscribersCounter<Self> {
return Publishers.SubscribersCounter<Self>(upstream: self, callback: callback)
}
}
操作员使我们可以注入发布者链的任何位置,并通过回调提供有趣的价值。在我们的案例中,有趣的价值将是订阅者的数量。
由于在上游和下游都注入了运算符,因此我们需要双向自定义管道实现,即自定义发布者,自定义订阅者,自定义订阅。在我们的情况下,它们必须是透明的,因为我们不需要修改流……实际上它将是Combine-proxy。
可能的用法:
1)当SubscribersCounter
发布者排在最后时,numberOfSubscribers
属性可以直接使用
let publisher = NotificationCenter.default
.publisher(for: UIApplication.didBecomeActiveNotification)
.countingSubscribers()
...
publisher.numberOfSubscribers
2)当它位于链的中间时,然后接收有关已更改订户数的回调
let publisher = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://www.google.com")!)
.countingSubscribers({ count in print("Observers: \(count)") })
.receive(on: DispatchQueue.main)
.map { _ in "Data received" }
.replaceError(with: "An error occurred")
这是实现:
import Combine
extension Publishers {
public class SubscribersCounter<Upstream> : Publisher where Upstream : Publisher {
private(set) var numberOfSubscribers = 0
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let callback: ((Int) -> Void)?
public init(upstream: Upstream, callback: ((Int) -> Void)?) {
self.upstream = upstream
self.callback = callback
}
public func receive<S>(subscriber: S) where S : Subscriber,
Upstream.Failure == S.Failure, Upstream.Output == S.Input {
self.increase()
upstream.receive(subscriber: SubscribersCounterSubscriber<S>(counter: self, subscriber: subscriber))
}
fileprivate func increase() {
numberOfSubscribers += 1
self.callback?(numberOfSubscribers)
}
fileprivate func decrease() {
numberOfSubscribers -= 1
self.callback?(numberOfSubscribers)
}
// own subscriber is needed to intercept upstream/downstream events
private class SubscribersCounterSubscriber<S> : Subscriber where S: Subscriber {
let counter: SubscribersCounter<Upstream>
let subscriber: S
init (counter: SubscribersCounter<Upstream>, subscriber: S) {
self.counter = counter
self.subscriber = subscriber
}
deinit {
Swift.print(">> Subscriber deinit")
}
func receive(subscription: Subscription) {
subscriber.receive(subscription: SubscribersCounterSubscription<Upstream>(counter: counter, subscription: subscription))
}
func receive(_ input: S.Input) -> Subscribers.Demand {
return subscriber.receive(input)
}
func receive(completion: Subscribers.Completion<S.Failure>) {
subscriber.receive(completion: completion)
}
typealias Input = S.Input
typealias Failure = S.Failure
}
// own subcription is needed to handle cancel and decrease
private class SubscribersCounterSubscription<Upstream>: Subscription where Upstream: Publisher {
let counter: SubscribersCounter<Upstream>
let wrapped: Subscription
private var cancelled = false
init(counter: SubscribersCounter<Upstream>, subscription: Subscription) {
self.counter = counter
self.wrapped = subscription
}
deinit {
Swift.print(">> Subscription deinit")
if !cancelled {
counter.decrease()
}
}
func request(_ demand: Subscribers.Demand) {
wrapped.request(demand)
}
func cancel() {
wrapped.cancel()
if !cancelled {
cancelled = true
counter.decrease()
}
}
}
}
}
答案 1 :(得分:1)
发布问题一段时间后,我编写了这个简单的扩展名。比@Asperi的解决方案简单得多。除了简单性之外,还不确定这两种解决方案之间的优缺点。
private enum CounterChange: Int, Equatable {
case increased = 1
case decreased = -1
}
extension Publisher {
func trackNumberOfSubscribers(
_ notifyChange: @escaping (Int) -> Void
) -> AnyPublisher<Output, Failure> {
var counter = NSNumber.init(value: 0)
let nsLock = NSLock()
func updateCounter(_ change: CounterChange, notify: (Int) -> Void) {
nsLock.lock()
counter = NSNumber(value: counter.intValue + change.rawValue)
notify(counter.intValue)
nsLock.unlock()
}
return handleEvents(
receiveSubscription: { _ in updateCounter(.increased, notify: notifyChange) },
receiveCompletion: { _ in updateCounter(.decreased, notify: notifyChange) },
receiveCancel: { updateCounter(.decreased, notify: notifyChange) }
).eraseToAnyPublisher()
}
}
以下是一些测试:
import XCTest
import Combine
final class PublisherTrackNumberOfSubscribersTest: TestCase {
func test_four_subscribers_complete_by_finish() {
doTest { publisher in
publisher.send(completion: .finished)
}
}
func test_four_subscribers_complete_by_error() {
doTest { publisher in
publisher.send(completion: .failure(.init()))
}
}
}
private extension PublisherTrackNumberOfSubscribersTest {
struct EmptyError: Swift.Error {}
func doTest(_ line: UInt = #line, complete: (PassthroughSubject<Int, EmptyError>) -> Void) {
let publisher = PassthroughSubject<Int, EmptyError>()
var numberOfSubscriptions = [Int]()
let trackable = publisher.trackNumberOfSubscribers { counter in
numberOfSubscriptions.append(counter)
}
func subscribe() -> Cancellable {
return trackable.sink(receiveCompletion: { _ in }, receiveValue: { _ in })
}
let cancellable1 = subscribe()
let cancellable2 = subscribe()
let cancellable3 = subscribe()
let cancellable4 = subscribe()
XCTAssertNotNil(cancellable1, line: line)
XCTAssertNotNil(cancellable2, line: line)
XCTAssertNotNil(cancellable3, line: line)
XCTAssertNotNil(cancellable4, line: line)
cancellable1.cancel()
cancellable2.cancel()
complete(publisher)
XCTAssertEqual(numberOfSubscriptions, [1, 2, 3, 4, 3, 2, 1, 0], line: line)
}
}