Swift Combine custom Publisher: Хранить ссылку на массив подписчиков? - PullRequest
3 голосов
/ 17 января 2020

Я пытаюсь написать пользовательский комбайн Publisher, который будет отправлять децибелы и временные метки из-под крана AVAudioEngine. Пройдя многочисленные уроки и видеоролики WWD C, я все еще не могу найти пример того, как Publisher отслеживает Subscriber, которые подписались на него.

public typealias AudioVolume = Double

public struct AudioVolumePublisher: Publisher {
    public typealias Output = AudioVolume
    public typealias Failure = Error
}

public class AudioVolumeSubscription<S: Subscriber>: NSObject, Subscription {
    private var subscriber: S?
    public var combineIdentifier = CombineIdentifier()

    public init(for subscriber: S) {
        self.subscriber = subscriber
    }

    public func request(_ demand: Subscribers.Demand) {
        ...
    }

    public func cancel() {
        subscriber = nil
    }
}

Я предполагаю, что AudioVolumePublisher должен хранить список своих активных подписчиков, но добавление свойства, такого как

var subscribers = [S]()

, не будет компилироваться, поскольку Subscriber имеет связанные типы. Является ли это даже правильным подходом к обработке Subscriber с, и если да, то как лучше всего их хранить? Является ли стирание типа моим единственным практическим вариантом?

Ответы [ 2 ]

3 голосов
/ 17 января 2020

Вы можете достичь своей цели с помощью CurrentValueSubject или PassthroughSubject.

Предполагая, что это форма, которую вы отправляете:

struct AudioVolume {
  let decimal: Decimal
  let timestamp: TimeStamp
}

создает тему

let audioPublisher = PassthroughSubject<AudioVolume, Never>()

всякий раз, когда вам нужно опубликовать sh новое значение:

let audioVolume = AudioVolume(....)
audioPublisher.send(audioVolume)

всякий раз, когда вам нужно подписаться на издателя:

audioPublisher
  .sink { audioVolume in
    // do something with audioVolume
  }
  .store(in: &cancellables) // retain the subscription in cancellables as long as you want to remain subscribed
0 голосов
/ 17 января 2020

В основном вам нужно завернуть полученного подписчика во что-то. Вы можете написать свою собственную абстракцию над этим, однако Combine уже предоставляет необходимые инструменты для этого: AnySubscriber (тип ластика, который вы упомянули).

public struct AudioVolumePublisher: Publisher {
    public typealias Output = AudioVolume
    public typealias Failure = Error

    private var subscriptions = [AudioVolumeSubscription]()

    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
        // some cleanup first, to remove disconnected subscribers
        subscribers = subscribers.filter { $0.subscriber != nil }

        // register the subscriber
        subscribers.append(AudioVolumeSubscription(subscriber))

        // signal availability to the subscriber
        subscriber.receive(subscription)
    }

Обратите внимание, что в этой реализации AudioVolumeSubscription не является универсальным c, это не обязательно должно быть:

public class AudioVolumeSubscription: NSObject, Subscription {
    // also note the weak reference, we don't want to keep the subscriber alive more
    // than needed, and this also avoids possible retain cycles
    private weak var subscriber: AnySubscriber<AudioVolume, Error>?

    // the generic responsibility was moved on the shoulders of the initializer
    public init<S: Subscriber>(for subscriber: S) where S.Input == AudioVolume, S.Failure == Error {
        self.subscriber = AnySubscriber(subscriber)
    }

Также обратите внимание, что это упрощенная c реализация, когда издатель сразу же сообщает о доступности. Не все издатели делают это, однако в вашем случае, похоже, это так.

...