概要
Swift初心者でCombineを少し触れたい人へ。
簡単なコードを説明します。
今日の例
今日はこれがわかるようになるのが目標!
わからなくてもOK。後で説明します。
わかった人は読まなくて良いです。
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a).map{b,a in a + b}
publisher.sink{ added in
print(added)
}
a.send(10)
まじ意味不明だと思いますが、標準出力(print)には何が表示されそうでしょうか?
答えは
4
11
2行表示されます。
最初の4は3 + 1
次の11は10 + 1です。
とりあえずimport
Combineのimportは
import Combine
でできます。iOS 13以上で使用可能。
Playgroundなどで簡単に試せます。
Combineの用語
Combineでとりあえず覚えるべき用語にPublisherとSubscriberがある。
| 名前 | 日本読み | 何してる |
|---|---|---|
| Publisher | パブリッシャー | データを発行 |
| Subscriber | サブスクライバー | データを購読 |
わからなくてもいいからとりあえず2単語暗記!
Publisher, Subscriber
とりあえずJust
Justは(多分)一番簡単なPublisherです。
とりあえず定義を見にいってみます。(一部抜粋)
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct Just<Output> : Publisher {
// ... (省略)
}
とりあえずPublisherを継承しているしPublisherですね。
iOS 13.0以上でしか使えないのも事実らしい。
このJustの説明には何が書かれているかというと、
A publisher that emits an output to each subscriber just once, and then finishes.
1つのアウトプットを各々のSubscriberに対して、ただ(just)1回出力するPublisherで、その後finishします。
この文章からは以下がわかります。
- Justは一回だけ何かを出力する。
- JustはPublisher
- Subscriberは複数いる可能性がある
- 1回出したら終了(finish)する
値を1つ出すpublisherだと無理やり覚えてみる。
sinkについて
例に出したコードで
publisher.sink{ added in
print(added)
}
というのがありました。
このsinkの説明を見てみると、
Attaches a subscriber with closure-based behavior to a publisher that never fails.
クロージャベースの振る舞いを持つSubscriberを失敗しないpublisherにアタッチします。
意味は不明ですが、subscriberをアタッチしてくれそうなので使ってみましょう。
例1
先ほどのJustでsinkを呼び出すとどうなるでしょうか?
Just(1).sink { r in
print(r)
}
これは1が表示されます。
publisher(Just)は1を一回だけ出力して、sinkすることでrにそれが入ってくるので1が表示されたようです。
例2
まだ説明していないCurrentValueSubject<Int, Never>(3)についても試してみましょう。
CurrentValueSubject<Int, Never>(3).sink { r in
print(r)
}
これは3が出力されます!
一体Justと何が違うのか、、、
CurrentValueSubjectについて
CurrentValueSubjectとは何でしょうか?
定義をみてみます。
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
final public class CurrentValueSubject<Output, Failure> : Subject
こいつはSubjectらしい。Publisherじゃなかった!
それとiOS 13.0以上でしか使えないのはJustと一緒ですね。
説明には、
A subject that wraps a single value and publishes a new element whenever the value changes.
*1つの値をラップして、値が変わった時の新しい値をPublishするSubject。*とあります。
PublishができるのはPublisherだけなのでは?
Subjectの定義も見に行きます。
public protocol Subject : AnyObject, Publisher
どうやらSubjectはPublisherの1種みたいですね。
Justと比べてみると、
| Just | 1つのアウトプットをただ(just)1回出力するPublisher |
| CurrentValueSubject | 1つの値をラップして、値が変わった時の新しい値をPublishするSubject |
Justとの違いは値が変わった時にその新しい値をpublishしてくれるということ!
試してみましょう。
import Combine
let a = CurrentValueSubject<Int, Never>(3)
a.sink{ added in
print(added)
}
a.value = 100
a.value = 10000
a.send(9999)
何が出力されそうでしょうか?
答えは
3
100
10000
9999
a.valueの変更のたびにprintしています。
またa.send()でも、値の変更を表現できるようです。
ラスト1行!
最初の例の大部分が説明できてきました。
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a).map{b,a in a + b}
publisher.sink{ added in
print(added)
}
a.send(10)
後、let publisher = b.combineLatest(a).map{b,a in a + b}が分かれば、なんとなくコードの意味がわかりそうです。
combineLatest
まずb.combineLatest(a)に注目しましょう。
これはどういう意味でしょうか?
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
だったので、aもbもどちらもPublisherですね。
combineLatestの説明をみてみましょう。
Subscribes to an additional publisher and publishes a tuple upon receiving output from either publisher.
追加されたPublisherを購読(Subscribe)して、各々のPublisherから受け取った出力のタプルをPublishする。
こいつはSubscribeもPublishもしているようです。
実際に使う
動かしてみる。
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a).sink{r in
print(r)
}
これだと何が出そうですか?
正解は
(1, 3)
そういえばcombineLatestはタプルを返すと先ほどいっていました。
aの値を変えてみましょう。3行コードを追加します。
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a).sink{r in
print(r)
}
a.value = 4
a.value = 5
a.send(6)
どうなりそうですか?
正解は
(1, 3)
(1, 4)
(1, 5)
(1, 6)
bの値は変わらないままaの値の変更によってprintされています。
動きはわかりましたでしょうか?
SubscribeもPublishもしているcombineLatestとは何者か?
定義をみてみると、
public func combineLatest<P>(_ other: P) -> Publishers.CombineLatest<Self, P> where P : Publisher, Self.Failure == P.Failure
長くて意味は不明ですが、返しているのは、Publishers.CombineLatest<Self, P>。
Publishers.CombineLatestは
public struct CombineLatest<A, B> : Publisher
でPublisherです。
なので、b.combineLatest(a)は2つのPublisherを1つのPublisherにするような関数です。
map
mapは
Transforms all elements from the upstream publisher with a provided closure.
上流のPublisherから来る全ての要素を与えられたクロージャーを使って変形する。
全部変形するらしい。
定義をみてみると
public func map<T>(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map<Self, T>
結局こいつもPublisherを返す!
例1
先ほどcombineLatestで出した例。
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a).sink{r in
print(r)
}
a.value = 4
a.value = 5
a.send(6)
結果は、
(1, 3)
(1, 4)
(1, 5)
(1, 6)
でしたね。
このコードのcombineLatestの後ろにmapをつけてみます。
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a)
.map{(b,a) in a+b}
.sink{r in print(r)}
a.value = 4
a.value = 5
a.send(6)
何が返りそうでしょうか?
正解は、
4
5
6
7
これは
(1, 3)
(1, 4)
(1, 5)
(1, 6)
この結果の各々2つの値を足していることがわかります。
例2
.map{(b,a) in a+b}を.map{(b,a) in a}に変えてみました。
出力は何になりますか?
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a)
.map{(b,a) in a}
.sink{r in print(r)}
a.value = 4
a.value = 5
a.send(6)
正解は
3
4
5
6
まとめ
| Combineを使うには | iOS 13.0以上でimport Combine
|
| Justとは | 値を1回だけ出力するPublisher |
| sinkとは | Publisherの出力した値を購読(Subscribe)して処理できる |
| CurrentValueSubject | 値を変化でき、変化した値を出力するPublisher(Subject) |
| CurrentValueSubjectの値を変更するには? | CurrentValueSubject.valueに代入するか、CurrentValueSubject.send(value)に値を入力 |
| combineLatestとは | 2つのpublisherを1つのpublisherにして、値をタプルで返す。 |
| mapとは | publisherからきた値を全部変形する |
目標の以下のコードの動きは理解できましたか?
import Combine
let a = CurrentValueSubject<Int, Never>(3)
let b = Just(1)
let publisher = b.combineLatest(a).map{b,a in a + b}
publisher.sink{ added in
print(added)
}
a.send(10)