【Swift/Combine】shareメソッドの使い方!publisherの共有とmulticastとの違い

【Swift/Combine】shareメソッドの使い方!publisherの共有とmulticastとの違い

この記事からわかること

  • SwiftCombineフレームワーク
  • shareメソッド使い方
  • publisher共有する方法
  • multicastの使い方と違い

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

公式リファレンス:Combine Framework

環境

shareメソッドの使い方

公式リファレンス:shareメソッド

func share() -> Publishers.Share<Self>

sharepublisher出力を複数のsubscriber間で共有するためのメソッドです。使い所としてはサーバーへのリクエストなど重い処理を行うpublisherの結果を複数の箇所から参考したい際にshareしたpublisherを使用することで1回のサーバーリクエスト処理(ストリーム)だけで購読対象全てにOutputもしくはErrorを流すことができます。

また購読対象が1つでもあればストリームは起動しますが、複数の購読対象がいる場合は全ての購読が解除されるまでストリームは破棄されません

実装サンプルは公式に記載されていたコードが非常にわかりやすかったです。ここでは「3回だけ0~100のランダムな数値が流れるストリーム」を2つのsubscriberが購読しています。

// 3回だけ0~100のランダムな数値が流れるストリーム
let sharedPublisher = (1...3).publisher
    .delay(for: 1, scheduler: DispatchQueue.main)
    .map( { _ in return Int.random(in: 0...100) } )
    .print("Random")
    .share() // 共有する

// 購読者1
let cancellable1 = sharedPublisher
    .sink { print ("Stream 1 received: \($0)")}
// 購読者2
let cancellable2 = sharedPublisher
    .sink { print ("Stream 2 received: \($0)")}

出力されるログを確認すると2つのsubscriber結果が同じ数値になっていることが確認できます。これは同じストリームを購読しているため同じ数値になります。

Random: receive value: (39)
Stream 2 received: 39
Stream 1 received: 39
Random: receive value: (54)
Stream 2 received: 54
Stream 1 received: 54
Random: receive value: (33)
Stream 2 received: 33
Stream 1 received: 33
Random: receive finished

試しにshareをコメントアウトしてみると・・・

let noSharedPublisher = (1...3).publisher
    .delay(for: 1, scheduler: DispatchQueue.main)
    .map( { _ in return Int.random(in: 0...100) } )
    .print("Random")
    // .share() // 共有しない

出力されるログは以下のように2つのsubscriber異なる数値となり別のストリームを購読していることがわかります。

Random: receive value: (49)
Stream 1 received: 49
Random: receive value: (4)
Stream 1 received: 4
Random: receive value: (97)
Stream 1 received: 97
Random: receive finished
Random: receive value: (56)
Stream 2 received: 56
Random: receive value: (81)
Stream 2 received: 81
Random: receive value: (14)
Stream 2 received: 14
Random: receive finished

購読前の値は受け取れない

shareでは同じストリームを共有することができますが購読タイミングが異なる場合の挙動には注意が必要です。publisherコールドストリームなので購読されたタイミングで値が流れ出します。例えば以下のような「1秒ごとに現在の日時が流れるストリーム」があるとします。

// 1秒ごとに現在の日時が流れるストリーム
let sharedPublisher = Timer.publish(every: 1.0, on: .main, in: .common)
    .autoconnect()
    .share()

// 購読者1
let cancellable1 = sharedPublisher
    .sink { value in
        print("Subscriber 1 received: \(value)")
    }

var cancellable2: AnyCancellable?
// 購読者2
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    cancellable2 = sharedPublisher
        .sink { value in
            print("Subscriber 2 received: \(value)")
        }
}

subscriberの購読タイミングを少しずらしてログを出力してみると以下のようになりSubscriber 2では購読前に流れた13:35:5513:35:56の値は受け取れていないことが確認できます。

Subscriber 1 received: 2025-01-08 13:35:55 +0000
Subscriber 1 received: 2025-01-08 13:35:56 +0000
Subscriber 1 received: 2025-01-08 13:35:57 +0000
Subscriber 2 received: 2025-01-08 13:35:57 +0000
Subscriber 1 received: 2025-01-08 13:35:58 +0000
Subscriber 2 received: 2025-01-08 13:35:58 +0000
Subscriber 1 received: 2025-01-08 13:35:59 +0000
Subscriber 2 received: 2025-01-08 13:35:59 +0000

multicastメソッドの使い方

公式リファレンス:multicastメソッド

func multicast<S>(_ createSubject: @escaping () -> S) -> Publishers.Multicast<Self, S> where S : Subject, Self.Failure == S.Failure, Self.Output == S.Output

multicastshareと同じくpublisher出力を複数のsubscriber間で共有するためのメソッドです。1点大きく異なるのはmulticastしたpublisher購読してもストリームが起動しないことです。ストリームを起動させるためにはconnectメソッドを明示的に呼びだす必要があります。

また引数には任意のsubjectを渡す必要があります。

この特性によりshareであった「購読前の値は受け取れない」問題を解消することができます。先ほどと同じpublisherでのコードの差分を見てみます。

// 1秒ごとに現在の日時が流れるストリーム
let sharedPublisher = Timer.publish(every: 1.0, on: .main, in: .common)
    .autoconnect()
    .multicast(subject: PassthroughSubject<Date, Never>()) 

// 購読者1
let cancellable1 = sharedPublisher
    .sink { value in
        print("Subscriber 1 received: \(value)")
    }

var cancellable2: AnyCancellable?
var connection: Cancellable?

// 購読者2
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    cancellable2 = sharedPublisher
        .sink { value in
            print("Subscriber 2 received: \(value)")
        }
    // connectを実行してストリームを起動する
    connection = sharedPublisher.connect() 
}

出力されたログは以下のようにsubscriber間で差分がなくなっていることがわかります。connectメソッドを呼び出したタイミングからストリームが流れ始めるのでsubscriber1では購読の準備だけしている状態になります。

Subscriber 1 received: 2025-01-08 13:55:23 +0000
Subscriber 2 received: 2025-01-08 13:55:23 +0000
Subscriber 1 received: 2025-01-08 13:55:24 +0000
Subscriber 2 received: 2025-01-08 13:55:24 +0000
Subscriber 1 received: 2025-01-08 13:55:25 +0000
Subscriber 2 received: 2025-01-08 13:55:25 +0000
Subscriber 1 received: 2025-01-08 13:55:26 +0000
Subscriber 2 received: 2025-01-08 13:55:26 +0000

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index