web-dev-qa-db-ja.com

Swiftカスタムパブリッシャーを結合:サブスクライバーの配列への参照を保存しますか?

Publisherタップからデシベルとタイムスタンプを送信するカスタムCombine AVAudioEngineを記述しようとしています。多数のチュートリアルとWWDCビデオを実行した後、私はstillが、購読しているPublishersを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には型が関連付けられているため、コンパイルされません。これはSubscribersを処理するための適切なアプローチでもありますか?そうであれば、それらを格納するための最良の方法は何ですか?タイプ消去は私の唯一の実用的なオプションですか?

3
NRitH

基本的に、受信したサブスクライバーを何かにラップする必要があります。これについて独自の抽象化を作成することもできますが、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はジェネリックではないことに注意してください。

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)
    }

また、これは単純な実装であり、パブリッシャーはすぐに可用性を通知します。すべてのパブリッシャーがこれを行うわけではありませんが、あなたの場合はそうです。

0
Cristik