【Swift UI/Combine】PassthroughSubjectとは?store(in:)メソッド

この記事からわかること

  • SwiftCombineフレームワーク
  • PassthroughSubject特徴使い方
  • CurrentValueSubjectとの違い
  • store(in:)メソッドの使い方
  • &(アンパサンド)とは?

index

[open]

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

みんなの誕生日

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

posted withアプリーチ

SwiftのCombineフレームワークのPassthroughSubjectについてまとめていきます。まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

PassthroughSubjectとは?

公式リファレンス:PassthroughSubject

final class PassthroughSubject<Output, Failure> where Failure : Error

PassthroughSubjectとは非同期処理やデータバインディングなどための機能を提供するCombineフレームワークのPublisherの1つです。Publisherはデータストリームを生成するためのオブジェクトであり、時間の経過と共に変化する一連の値の変化を配信(通知を送信)する特徴があります。

RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する

さらにPublisherの中のSubject(被写体)にカテゴライズされるのがPassthroughSubjectです。Combineフレームワークで定義されているSubjectとは「Publisherプロトコルを継承し、値を発行する役割」と、「他のPublisherが送信するデータストリームを受け取るSubscriber」の両方の役割を持つオブジェクトです。

同じSubjectの仲間にCurrentValueSubjectが定義されています。Combineフレームワークに関するPublisherやSubject、ストリームなどたくさんの単語が出てきましたが詳細は以下を参考にしてください。

おすすめ記事:【Swift UI】Combineフレームワークの使い方!PublisherとSubscriberの違い

PassthroughSubject:ここまでのまとめ

特徴とCurrentValueSubjectとの違い

CurrentValueSubjectPassthroughSubject、2つの大きな違いは値を保持するかどうかです。

CurrentValueSubjectは常に最新値を保持してくれるのでログイン状態や設定値など参照する可能性のある場合に活用できそうです。

PassthroughSubjectは値自体を保持しないので常に最新の値に更新され続けるようなWeb API取得や一度きりの状態変化を取得したい場合などが良いのかもしれません。

PassthroughSubjectの役割

PassthroughSubjectクラスは値の変更を検知する箱のようなイメージです。箱の中に値が格納されたことを周りに知らせる(ブロードキャストする)役割(sendメソッド)や、その箱の変更を観測し変更があった場合に任意の処理を行わせる役割(sinkメソッド)が用意されています。

例えば以下はPassthroughSubjectクラスを使用した例です。

// PassthroughSubjectを定義
let subject = PassthroughSubject<Int, Never>()

// PassthroughSubjectを購読
let cancellable = subject.sink { value in
    print("値は:", value)
}

// 値を発行
subject.send(1)
subject.send(2)
subject.send(3)

// 購読をキャンセル
cancellable.cancel()

// もう一度値を発行(購読がキャンセルされているため表示されない)
subject.send(4)
値は: 1
値は: 2
値は: 3

PassthroughSubjectは定義の通り、<Output, Failure>の2つのデータ型を保持します。OutputにはStringInt、値が存在しないVoidなどを渡すことができ、Failureには任意のエラー型を渡すことが可能です。

final class PassthroughSubject<Output, Failure> where Failure : Error

Swift UIで使用してみる

Swift UIの中で使用してみたいと思います。今回はWebPageStateクラスのプロパティにPassthroughSubjectクラスを適応させてみたいと思います。またアプリ内でシングルトンになるようにsharedプロパティを定義しておきます。

class WebPageState {
    static let shared = WebPageState()
    public let reload = PassthroughSubject<Void, Never>()
}

異なるView同士でイベント通知を検知し合えるのがメリットの1つなので2つのビューを定義してみました。本来ならそれぞれのViewModelを介することが多いかもしれませんがわかりやすくそのまま使用しています。TestWebView側ではonAppearのタイミングで購読を開始しています。この際に返却されるAnyCancellableオブジェクトをViewのプロパティに格納しておきます。

struct TestWebView: View {
    
    private let state = WebPageState.shared
    @State  var cancellable:AnyCancellable?
    
    var body: some View {
        VStack{
            Text("WebView")
            TestButtonView()
        }.onAppear {
            // Viewのプロパティとして受け取らないと検知できない
            // let cancellable = state.reload.sink { ×
            cancellable = state.reload.sink {
                print("Webページのリロード処理を実行する")
            }
        }.onDisappear{
            // 破棄されるタイミングで購読をキャンセルする 
            cancellable?.cancel()
        }
    }
}

struct TestButtonView: View {
    private let state = WebPageState.shared
    var body: some View {
        VStack{
            Button {
                state.reload.send()
            } label: {
                Text("イベントを発行")
            }
        }
    }
}

発行する側は特に変わったところはありません。このように異なるViewやクラスなどからイベントを検知してそれに伴う処理が実行できるようになります。

管理する対象を増やす

ではWebPageStateクラスにPassthroughSubjectを追加してみます。

class WebPageState {
    static let shared = WebPageState()
    public let reload = PassthroughSubject<Void, Never>()
    public let back = PassthroughSubject<Void, Never>()
}

単純に以下のように実装するとcancellableの中身が上書きされてreloadがイベントを検知できなくなってしまいます。


struct TestWebView: View {
    
    private let state = WebPageState.shared
    @State  var cancellable:AnyCancellable?

    var body: some View {
        VStack{
            Text("WebView")
            TestButtonView()
        }.onAppear {
            cancellable = state.reload.sink {
                print("Webページのリロード処理を実行する")
            }
            cancellable = state.back.sink {
                print("Webページのバック処理を実行する")
            }
        }.onDisappear{
            cancellable?.cancel()
        }
    }
}

store(in: &cancellables)

動作するようにするにはstore(in:)メソッドを使用します。このメソッドはAnyCancellableをSetの中に自動で格納してくれます。定義を見てみるとinoutが指定されているので「参照渡し」になります。呼び出す際には&(アンパサンド)を変数の前に付与する必要があります。

final func store(in set: inout Set<AnyCancellable>)

実装コードは以下のようになります。キャンセルする方法もremoveAllメソッドに変更しています。


struct TestWebView: View {
    
    private let state = WebPageState.shared
    @State  var cancellables:Set<AnyCancellable>
    
    var body: some View {
        VStack{
            Text("WebView")
            TestButtonView()
        }.onAppear {
            state.reload.sink {
                print("Webページのリロード処理を実行する")
            }.store(in: &cancellables)
            state.back.sink {
                print("Webページのブラウザバック処理を実行する")
            }.store(in: &cancellables)
        }.onDisappear {
            cancellables.removeAll()
        }
    }
}

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

searchbox

スポンサー

ProFile

ame

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

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

New Article

index