LoginSignup
1
1

Combineをどうしても理解したいということで、、、part1

Last updated at Posted at 2023-12-16

どうしてCombineを学びたいと思ったのか?

一番最初に作ったアプリで、MVVMアーキテクチャを使用し、1View1ViewMdoelへ書き換えたいという場面がありました。ここで問題になったのが、二つのViewModelで同じ値を保持させたいということです(値の変化も検知し、Viewへ反映させる)。その際に、assgin(to:)というメソッドを使った書き方を教えていただき、やりたいことが達成できました。その経験から『Combineの一部の機能だけでこんなできちゃうの?めちゃくちゃ便利やん?』と思ったのが、勉強に至った経緯です。現在チーム開発も行っており、将来的にも役に立つかなと思っています。

流れ

  1. Combineとは?
  2. 今回のキーワード
  3. 値を流し、受け取り、ルールを定義してみる!
  4. サブスクライバーの登録を無効にする
  5. 片方のサブスクライバーの登録を無効化する
  6. 複数のサブスクライバーの登録を一気解除する
  7. まとめ

Combineとは?

公式の一部を引用させてもらいました。

イベント処理演算子を組み合わせて、非同期イベントの処理をカスタマイズする。Combine フレームワークは、時間の経過とともに値を処理するための宣言型 Swift APIを提供します。これらの値は、さまざまな種類の非同期イベントを表すことができます。Combine は、パブリッシャーが時間の経過とともに変化する可能性のある値を公開し、サブスクライバーパブリッシャーからそれらの値を受け取ることを宣言します。

今回ポイントとなるところが、 『サブスクライバー(Subscriber)』『パブリッシャー(Publisher)』 です。
ざっくり説明すると、値を流すのが、パブリッシャーで、流された値を受け取るのがサブスクライバーです。僕は下の図のようなイメージを持っています。電波塔は誰が聞くかかわからない放送を流します(パブリッシャーの役割)。その放送を聴きたい時はラジオのチューナーを合わせて聴く(サブスクライバーの役割)と言った感じです。
サブスクライバーについて補足です。パブリッシャーに対して、登録の処理を行うオブジェクトを指します。例えば、sink()で処理の登録を行なったプロパティを指すと言った感じです。
値を流すものをストリームと言ったりもするそうです。僕は管がイメージしやすかったので、今回は管を用いて説明させていただきます。

combine1

今回のキーワード

PassthroughSubject<Output,Failure>()
sink()
send()
cancel()
AnyCancellable型

値を流し、受け取り、ルールを定義してみる!

パブリッシャー・・・PassthroughSubject<Int,Never>()
サブスクライバー・・・sink()で登録を行なったsubjectA

import Combine //🟥Combineフレームワークをインポートする。

let subject = PassthroughSubject<Int,Never>()//🟦値を流すための 管を作る

let subjectA = subject.sink { num in //🟦ルールを定義する。
    print("PassthroughSubjectgaが受け取った値は:\(num)")
}

subject.send(1) //🟦管に値を流し込む
subject.send(2)
subject.send(3)

PassthroughSubject<Int,Never>()』はのような役割を持っています。今回の場合は、Int型を受け取り、Int型を出力します。第二引数部分はエラーが起こらないNever型になっています。これに対して、『.send()』を使ってに値を送ります。その送られてきた値をどのように処理するのかを『sink()』のクロージャ内で定義しています。ここから、いろいろ変化していきます!!!

上の例で出てきたPassthroughSubjectはSubjectプロトコルに適合しているのですが、SubjectプロトコルもPublisherプロトコルを併せ持っているので、Pubisherと同様の働きをすることが可能です。そしてもう一つ。PassthroughSubjectと似た

サブスクライバーの登録を無効にする

sink()のクロージャの中で行っていた処理の登録を無効にさせます。

let subject = PassthroughSubject<Int,Never>()

let subjectA = subject.sink { number in
    print("subjectA:",number)
}

subject.send(1)// A 1
subjectA.cancel()
subject.send(2)// 出力されない
subject.send(3)// 出力されない

ここで登場するのが『cancel』メソッドです。このcancelメソッドが、どうして値の流れを止めることができるのか説明します。まず、『sink』メソッドの返り値をドキュメントで確認すると、『AnyCancellable型』になっています。このAnyCancellable型はcancelメソッドを持っており、これを実行すると、値の流れを止めることができます。
今回の場合はsubjectAがcancelメソッドをsend(2)の直前で実行したため、subjectAに対して、send(2),send(3)を送ったとしても、sinkメソッドの処理が走らなかったというわけです。

片方のサブスクライバーの登録を無効化する

値の流れを二つの箇所で受け取りたいとし、尚且つ、subjectAに対しては途中で処理を無効化させたいときのサンプルコードです。

let subject = PassthroughSubject<Int,Never>()

let subjectA = subject.sink { number in
    print("subjectA:",number)
}

let subjectB = subject.sink { number in
    print("subjectB:",number)
}

subject.send(1)// B 1,A 1
subjectA.cancel()
subject.send(2)//B 2
subject.send(3)//B 3

subjectBでは全ての値を受け取り、subjectAではsend(1)以降のルールを行いたくないと言った場合です。sendで1が送られた後に、subjectAに登録の解除をさせます。

複数のサブスクライバーの登録を一気解除する

Set型のプロパティに追加

一つ一つキャンセルしてもいいですが、それが50個、100個と多くなっていくとさすがに骨が折れますよね。
なので、配列にAnyCancellable型の要素を追加していき、一気にcancelを実行すると楽にできそうです。しかし、ここで配列には追加していく要素は、キャンセルするために追加していくものなので、重複する意味はありません。(クリーニングのチケットを2枚もらっても、戻ってくる服は一枚しかない。みたいなイメージです。2枚もらう意味はあまりありませんよね。)この重複を避けるために使われるのがSet型です。重複した要素を自動で弾いてくれます。
*Set型に追加したい場合はinsertで要素を追加していきます。

forEachで取り出しキャンセル

setプロパティに追加された要素をforEachを使用して順次取り出し、キャンセルしていきます。

var set = Set<AnyCancellable>()
let subject = PassthroughSubject<Int,Never>()


let subjectA = subject.sink { number in
    print("A",number)
}

let subjectB = subject.sink { number in
    print("B",number)
}

let subjectC = subject.sink { number in
    print("C",number)
}

set.insert(subjectA)
set.insert(subjectB)
set.insert(subjectC)
set.count  //🟦3つのサブスクライバーが入っている状態
subject.send(10)
subject.send(20)
subject.send(30)
set.forEach{
    $0.cancel() //🟦一気に登録を解除する
}
subject.send(40) //出力されない

実行結果-------順番は固定されていません
C 10
A 10
B 10
C 20
A 20
B 20
C 30
A 30
B 30

まとめ

  1. 値を流す(パブリッシャー)
  2. 登録された値の処理が書かれたオブジェクト(サブスクライバー)
  3. 登録の解除(cancel)

上の3つについて説明させてもらいました。次回は@Publishedやオペレーターについてアウトプットしていきたいと思います!
間違っている箇所があれば、教えていただけると大変嬉しいですのでよろしくお願いします!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1