【Swift/Combine】shareメソッドの使い方!publisherの共有とmulticastとの違い
この記事からわかること
- SwiftのCombineフレームワーク
- shareメソッドの使い方
- publisherを共有する方法
- multicastの使い方と違い
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:16.0
- iOS:18.0
- Swift:5.9
- macOS:Sonoma 14.6.1
shareメソッドの使い方
func share() -> Publishers.Share<Self>
share
はpublisher
の出力を複数の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:55
と13: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メソッドの使い方
func multicast<S>(_ createSubject: @escaping () -> S) -> Publishers.Multicast<Self, S> where S : Subject, Self.Failure == S.Failure, Self.Output == S.Output
multicast
もshare
と同じく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
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。