はじめに

本書は Combine をこれから学んでみようという人のための本です。

Combine は Swift でリアクティブプログラミングを行うためのフレームワークです。Apple プラットフォーム(iOS、iPadOS、watchOS、tvOS、macOS)向けの App を開発するときに、様々な処理を宣言的に記述することが可能になります。

Combine が登場したのは 2019 年 6 月の WWDC でした。登場時点では、最新の OS でしか動作しなかったため、まだ実際の App 開発で採用するのは難しい状況でした。それから 1 年たって 2020 年 9 月現在、もうすぐ次のバージョンの OS がリリースされようとしています。これからの App 開発において、Combine が動作しない OS はサポートしないという選択が現実的に可能な状況になりました。

したがって、今は Combine をはじめるちょうど良い機会です。本書は Combine を使い始めるための基本的な事柄について解説します。

本書の特徴

Combine については、Apple が WWDC のセッションで解説しているほか、解説書や解説記事が既に存在しています。それらを見て学ぶのが一番良い方法でしょう。

ただし、ここで落とし穴があります。

既存の解説の多くは、率直に言って、難しいです。

Combine の解説はたいていの場合、登場する用語や概念の説明から始まります。ここで、たくさんの用語や概念が一度に押し寄せてきます。この最初の導入部分でつまづきが発生しやすいです。結果として、Combine は敷居が高いものになってしまっている、と考えています。

基本的な事柄をもっと分かりやすく説明する解説書が存在するべきだ、という想いから、本書を執筆しました。本書では導入部分をできるだけ丁寧に説明するように心がけました。さらに説明の際に、具体的なコードを先に挙げるようにしました。

また、日本語で書かれた解説はまだそれほど多くありません。そこで、日本語で読める Combine の本を増やしたいという想いも、本書を執筆した動機のひとつです。

本書の前提知識

本書を読んでいただくにあたっての前提を記載しておきます。

Swift や Apple プラットフォーム向け App 開発の基本はある程度知っているものとします。また、開発環境として Mac と Xcode 11 以降が必要です。

Apple Developer Program への加入は、必須ではありません。ただ、加入していれば iPhone などの Apple デバイス上で動作を試すことが可能になります。

Combine 以外のフレームワーク

Swift で使えるリアクティブプログラミングのフレームワークは、Combine 以外にも RxSwift や ReactiveSwift などがあります。これらはサードパーティ製のフレームワークです。Combine が登場するよりも前から存在しており、広く使われてきました。一方、Combine は後発ですが標準フレームワークとして組み込まれているため、今後使われるようになっていくと考えられます。

もし既に RxSwift や ReactiveSwift を使ったことがあれば、Combine を理解する助けになるでしょう。ただし、用語や考え方が異なる点もありますので注意してください。

また、それらを使ったことがなくとも、本書を読んでいただいて Combine を理解していただけると考えています。

第1章 Combine の最初の一歩

1.1 学ぶ方針

私たちプログラマが新しいフレームワークを学ぼうとするとき、何を最初にやるべきでしょうか。プログラマであれば、言葉であれこれ説明する前に、実際にコードを書いて動かしてみるのが理解が早いだろうと考えています。

そこで、Combine を Xcode Playground を使って試してみます。

ここで早速、Combine が標準フレームワークであることが役に立ちます。サードパーティ製のフレームワークだったなら、まず何らかの方法でフレームワークを使えるようにしなければならないところでした。Combine は標準フレームワークなので、Xcode さえあればすぐに Combine のコードを書いて動かしてみることができます。

なお、Xcode の Playground の代わりに iPad(または Mac)の Swift Playgrounds を使うことも可能です。本書では Xcode を使うことにします。

では、お手元に Mac と Xcode 11 以降を用意してください。

1.2 値の送信と受信

Xcode で Playground を新規作成します。中身は空で良いため Blank を指定して作成します。そして、次のコードを書いて実行します。

リスト1.1: Swift コード

import Combine

 let subject = PassthroughSubject<String, Never>()

subject
    .sink { value in
        print("Received value:", value)
    }

subject.send("あ")
subject.send("い")
subject.send("う")
subject.send("え")
subject.send("お")

Playground 上でコードが無事に動作すれば、出力として次が表示されます。

リスト1.2: 出力結果

Received value: あ
Received value: い
Received value: う
Received value: え
Received value: お

では次に、このコードが何をしているかを見ていきましょう。

その前に、まだ Combine が何をするフレームワークなのかを説明していませんでした。Combine は、オブジェクトからオブジェクトにイベントを伝える仕組みを提供します。ここでいうイベントとは、GUI の操作やネットワーク通信など、App 内で発生した何らかの変化を伝えるものです。GUI や通信については後述することとして、ひとまずは、オブジェクトに何らかの値を渡すイベントを考えていきましょう。

リスト1.1 では、文字列の値を渡すイベントを扱います。イベントを伝える手段として Combine の PassthroughSubject クラスを使っています。

3 行目で定義している subjectPassthroughSubject のインスタンスで、イベントを中継する役目を果たします。この定義の場合、String 型の値を送受信します。

6 行目の sink メソッドは、subject でイベントを受信した際に実行する処理を指定します。このコードでは、受信した String 型の値を print を使って表示する処理を行っています。

10 行目の send メソッドは、subjectString 型の値を送信します。この値が受信処理に渡されて、6 行目で指定していた処理が実行されることになります。その結果が リスト1.2 です。

こうして、オブジェクトからオブジェクトに値を渡すイベントの処理が Combine で書けました。これが Combine を使ったコードのもっとも簡単な例のひとつです。

1.3 イベントの完了

先ほどのコードでは、イベントで値を渡すだけでした。これを少し発展させて、イベントの完了を渡すようにしてみます。

リスト1.3: Swift コード

import Combine

let subject = PassthroughSubject<String, Never>()

subject
    .sink(receiveCompletion: { completion in
        print("Received completion:", completion)
    }, receiveValue: { value in
        print("Received value:", value)
    })

subject.send("あ")
subject.send("い")
subject.send("う")
subject.send("え")
subject.send("お")
subject.send(completion: .finished)

無事に動作すれば、出力として次が表示されます。

リスト1.4: 出力結果

Received value: あ
Received value: い
Received value: う
Received value: え
Received value: お
Received completion: finished

リスト1.3 の 6 行目の sink メソッドでは、先ほどと異なり、ふたつのクロージャを指定しています。receiveValue は先ほどと同様にイベントを受信した際に実行する処理です。receiveCompletion が新しく増えたクロージャで、イベント完了を受信した際に実行する処理です。

17 行目の send メソッドでは、値を送信する代わりにイベント完了を意味する .finished を送信しています。この結果、5 行目で指定していたイベント完了を受信した際の処理が実行されることになります。

なお、イベント完了のあとでは、send メソッドで値を送信しても、受信処理は行われません。例えば、リスト1.3 の 12 行目から 17 行目を次のように変更してみます。

リスト1.5: Swift コード

subject.send("あ")
subject.send("い")
subject.send("う")
subject.send(completion: .finished)
subject.send("え")
subject.send("お")

実行してみると、イベント完了後の値の受信処理は行われないことが分かります。

リスト1.6: 出力結果

Received value: あ
Received value: い
Received value: う
Received completion: finished

1.4 イベントのエラー

イベントの完了は .finished の他にもうひとつあります。エラー終了です。

リスト1.7: Swift コード

import Combine

enum MyError: Error {
    case failed
}

let subject = PassthroughSubject<String, MyError>()

subject
    .sink(receiveCompletion: { completion in
        print("Received completion:", completion)
    }, receiveValue: { value in
        print("Received value:", value)
    })

subject.send("あ")
subject.send("い")
subject.send("う")
subject.send("え")
subject.send("お")
subject.send(completion: .failure(.failed))

今度は出力として次が表示されます(__lldb_expr_1 の部分は環境によって異なります)。

リスト1.8: 出力結果

Received value: あ
Received value: い
Received value: う
Received value: え
Received value: お
Received completion: failure(__lldb_expr_1.MyError.failed)

リスト1.7 の 7 行目で PassthroughSubject インスタンスを生成する際、イベントの値の型を String に、エラーの型を MyError に指定しています。

21 行目の send メソッドでは、値を送信する代わりにエラーを意味する .failure を送信しています。エラーの場合も .finished と同じ receiveCompletion クロージャが実行されます。

ところで、リスト1.1リスト1.3 ではエラーの型を Never にしていました。これは、エラーが発生しないことを意味する特別な指定です。リスト1.1 で使っていた 1 引数の sink は、エラーの型が Never の場合にのみ使用可能です。

1.5 この章のまとめ

Combine が扱うイベントについて整理しておきます。送受信されるイベントの内容は、以下の 3 種類があります。

  • イベント完了(.finished
  • エラー終了(.failure

値の型は、送受信に使うクラスによって指定されます。ここまでの例では、String 型を指定していました。

エラーの型も同様に、送受信に使うクラスによって指定されます。エラーの型を Never にすることで、エラーが起こらないイベント(値とイベント完了のみ)を送受信できます。

これ以降の章で Combine の用語や概念を説明していきますが、このイベントという概念が基本となります。まずはこれをおさえておいてください。

試し読みはここまでです。
この続きは、製品版でお楽しみください。