115
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swift Combine初めの1歩

Posted at

概要

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行表示されます。
最初の43 + 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)
}

これはが表示されます。

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)

115
67
1

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
115
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?