【Swift/Combine】combineLatestで複数条件を監視する方法!mergeやzipとの違い
この記事からわかること
- SwiftのCombineフレームワークの使い方
- combineLatestメソッドとは?
- 複数のpublisherを1つにまとめて監視する方法
- mergeやzipとの違いや使い分け
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
combineLatestメソッドの使い方
func combineLatest<P>(_ other: P) -> Publishers.CombineLatest<Self, P> where P : Publisher, Self.Failure == P.Failure
combineLatest
は複数のpublisherをまとめて1つのpublisherにして管理することができるメソッドです。例としてバリデーションロジックを実装する時など「全てがtrueの場合のみ処理を実装」する場合で考えてみます。
combineLatest
メソッドは1つのpublisherから呼び出し、引数に一緒に観測させたいpublisherを渡します。まとめた後のオペレーターからはタプルで流れてくるそれぞれの値を参照することが可能ですが、map
などを使用して1つの値を流すように変換すると見通しが良くなります。
let subjectA: PassthroughSubject<Bool, Never> = .init()
let subjectB: PassthroughSubject<Bool, Never> = .init()
let subjectC: PassthroughSubject<Bool, Never> = .init()
let cancellable = subjectA
.combineLatest(subjectB, subjectC)
.map { valueA, valueB, valueC in
return valueA && valueB && valueC
}
.sink { value in
if value {
print("All subjects are true.")
}
}
subjectA.send(true)
subjectB.send(false)
subjectC.send(true)
combineLatest
メソッドでまとめると全てのpublisherから値が流れてこないと処理が走りません。以下のようにsubjectC
だけsend
メソッドが送られていない状態の場合はその後のオペレーターなどは動作しなくなります。
subjectA.send(true)
subjectB.send(false)
全ての値が一度でも流れていれば、それ以降はいずれかの値が流れてくるたびに動作します。
subjectA.send(true)
subjectB.send(false)
subjectC.send(true)
sleep(3)
subjectB.send(true) // このタイミングで All subjects are true. が出力される
mergeやzipとの違いと使い分け
似たようなpublisherをまとめるメソッドにmerge
やzip
があります。それぞれの違いは以下の通りです。
- combineLatest:最新の値を使って結合。最低でも1回は値が送信されるまで結合されない。
- merge:複数のpublisherからのイベントを単一のpublisherに順次結合
- zip:対応する位置の値をペアにして出力。最小のpublisherが完了すると結合
mergeメソッド
func merge<P>(with other: P) -> Publishers.Merge<Self, P> where P : Publisher, Self.Failure == P.Failure, Self.Output == P.Output
mergeメソッドは複数のpublisherからのイベントを単一のpublisherに順次結合するメソッドです。実行してみるとわかりますが、複数のpublisherをmergeしても流れてくる値は1つのみです。つまり両方のイベントが発火するタイミングで同じpublisherから値が観測することができるので全てのpublisherの値が揃うのを待つわけではありません。
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<Int, Never>()
let cancellable = publisher1
.merge(with: publisher2)
.sink { value in
print(value)
}
publisher1.send(1) // 出力: 1
publisher2.send(2) // 出力: 2
zipメソッド
func zip<P>(_ other: P) -> Publishers.Zip<Self, P> where P : Publisher, Self.Failure == P.Failure
zip
メソッドは複数のpublisherに対応する位置の値をペアにして出力します。値が流れるのはzip
でまとめている値が全て揃った時です。
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<String, Never>()
let publisher3 = PassthroughSubject<Bool, Never>()
let cancellable = publisher1
.zip(publisher2, publisher3)
.sink { value in
print(value)
}
publisher1.send(1)
publisher2.send("A")
publisher3.send(false) // 出力: (1, "A", false)
値が一度流れると再度全ての値が揃うまで動作することはありません。例えば以下のようなイベントの流れ方をした場合は「11行目の3つの値が揃ったタイミング」で値が流れます。
publisher1.send(1)
publisher2.send("A")
publisher3.send(false) // 出力: (1, "A", false)
sleep(2)
publisher1.send(2)
sleep(2)
publisher2.send("B")
publisher3.send(true) // 出力: (2, "B", true)
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。