概要
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)