【Swift/Combine】combineLatestで複数条件を監視する方法!mergeやzipとの違い

この記事からわかること

  • SwiftCombineフレームワーク使い方
  • combineLatestメソッドとは?
  • 複数publisher1つにまとめて監視する方法
  • mergezipとの違い使い分け

index

[open]

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

みんなの誕生日

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

posted withアプリーチ

公式リファレンス:Combine Framework

環境

combineLatestメソッドの使い方

公式リファレンス: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. が出力される

もちろん定義されているPassthroughSubjectが保持するデータ型が異なっていても正常に動作します。

let subjectA: PassthroughSubject<Bool, Never> = .init()
let subjectB: PassthroughSubject<Int, Never> = .init()
let subjectC: PassthroughSubject<Bool, Never> = .init()

let cancellable = subjectA
    .combineLatest(subjectB, subjectC)
    .sink { valueA, valueB, valueC in
        print("\(valueA)/\(valueB)/\(valueC)")
    }

subjectA.send(true)
subjectB.send(0)
subjectC.send(true)

mergeやzipとの違いと使い分け

似たようなpublisherをまとめるメソッドにmergezipがあります。それぞれの違いは以下の通りです。

mergeメソッド

公式リファレンス: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メソッド

公式リファレンス: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)

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

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

searchbox

スポンサー

ProFile

ame

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

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

New Article

index