【Swift】Combine入門レベルのまとめ

## この記事でわかること
Combineを利用する上で理解が必須になってくる

- Publisher
- Subscriber
- Subject
- Operator

の関係性をシンプルなサンプルコードを使って説明していきます。

## Combineとは
iOS13から追加されたApple純正のFrameworkです。
Swiftのみで利用可能で、Objective-Cでは利用できないようです。

Combineの特徴を簡単に言葉にしてみると、
`イベントを発行する側と受け取る側に分かれて、あるイベントが発行されたら、それを受け取った側の処理が走ることを容易にしたフレームワーク`
という感じで捉えています。

公式ドキュメントを呼んでみるとこんな説明が、

<blockquote>
By adopting Combine, you’ll make your code easier to read and maintain, by centralizing your event-processing code and eliminating troublesome techniques like nested closures and convention-based callbacks.
</blockquote>

要するに、Swiftの特徴である、クロージャを使ったコールバックでコードの可読性が下がったり、処理が難しくなってしまうことを防ぎ、コードのメンテナンス性の向上が期待できる。

という感じです。個人的にクロージャのコールバック地獄のコードは辛いのでこれは嬉しいです。

### 個人的なメモ
今回、Combineのサンプルを漁っている時に感じたことは、
「これって、`RxSwift`にめちゃくちゃ似てるじゃないですか。」

MVVMアーキテクチャの開発は、`RxSwift`一択だと思っていましたが、純正って響きが良きです。

あと、iOS13〜ということで、`SwiftUI`との相性が良さそうなのも🙆‍♂️

2021年にiOS15がリリースされたので、これから新規開発するアプリは、iOS13-15をサポートすればいいと考えると、そろそろ`SwiftUI`が本格的に導入され、それに伴ってCombineも来るのか!?って思っちゃいました。

## Combineの主な登場人物
| 名前       | 役割                                             | 
| ---------- | ------------------------------------------------ | 
| Publisher  | 発行者<br>イベントを発行する側の人間             | 
| Subscriber | 購読者<br>イベントを受け取って処理をする側の人間 | 
| Operator   | 再発行者<br>流れてきたイベントを加工する側の人間 | 

この人達の関係性のイメージはこんな感じですかね。
![スクリーンショット 2022-01-30 14.00.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/01c62745-d5bd-f3d4-c784-d36d2ac795be.png)

## PublisherとSubscriberについて
Combineを利用する上で、極めてシンプルで簡単なサンプルを書いてみました。

```swift
import UIKit
import Combine

// 発行者を生成する
let publisher = ["スパイダーマン", "タイタニック", "ワイルド・スピード"].publisher

// 購読登録を行う
publisher.sink { string in
    print("\(string)を鑑賞します")
}

// 出力
スパイダーマンを鑑賞します
タイタニックを鑑賞します
ワイルド・スピードを鑑賞します
```

ここで、AmazonPrimeやNetflixといった、各種配信サブスクを想像してください。
配列の中身は、「発行者」の持っている「コンテンツ」であり、
このコードの関係性が、「発行者」->「購読者」の関係になっていることがなんとなくわかると思います。

### sinkについて
ここで、`sink`というメソッドが登場しましたが、こちらについては、ドキュメントに
> Attaches a subscriber with closure-based behavior to a publisher that never fails.

と書かれていますが、

クロージャ処理をするところが「Subscriber(購読者)」の振る舞いとなり、それを定義済の「Publisher(発行者)」と紐付ける。

という解釈で良いと思います。

## Subjectについて
先程のコードは、`Subject`を使わないで実装しましたが、次は、`Subject`を使っています。
`Subject`は`Publisher`を継承したプロトコルです。

`Subject`を使うことで、外部から値を受け取ってそれを通知することができるようになります。

とりあえず、サンプルコードを見てみることにします。

```swift
let subject = PassthroughSubject<String, Never>()

subject.send("アイアンマン")

// 購読登録
subject.sink { completion in
    print("complete")
} receiveValue: { string in
    print("\(string)")
}

subject.send("アベンジャーズ")

subject.send(completion: .finished)

subject.send("エターナルズ")

// 出力
アベンジャーズを鑑賞します
```

1番目のサンプルとは、`Subscriber`が値を受け取る方法が異なるのがわかると思います。
さっきは最初から定義された値が`Subscriber`に渡されましたが、今回は、外部から値を受け取っているのがわかります。

また、購読登録前、`completion`を投げた後は、`Subject`に対して`send()`しても、購読者が持っている振る舞いは実行されないことがわかります。

## Operatorについて
これまでのサンプルコードは、`Operator`をあえて登場させませんでしたので、今回はOperetorを登場させたサンプルコードです。

`Operator`については、冒頭でも説明しましたが、
`Publisher`によって発行された値を、`Subscriber`が購読する前に、値をキャッチして変換することができます。

今回は、`map(_:)`という`Operator`を例にしたサンプルです。

```swift
let publisher = [1,2,3].publisher
publisher.sink { value in   // valueはint型で取得される
    print("アベンジャーズ\(value)を鑑賞します。")
}

//アベンジャーズ1を鑑賞します。
//アベンジャーズ2を鑑賞します。
//アベンジャーズ3を鑑賞します。

let formatter = NumberFormatter()
formatter.numberStyle = .spellOut

publisher.map { value in
    // 値をString型へ変更
    formatter.string(from: NSNumber(integerLiteral: value)) ?? ""
}.sink { string in
    print("アベンジャーズ\(string)を鑑賞します。")
}

//アベンジャーズoneを鑑賞します。
//アベンジャーズtwoを鑑賞します。
//アベンジャーズthreeを鑑賞します。
```

`map(_:)`はシンプルなので、ほぼ説明不要かと思いますが、この`Operator`を利用することで、`Subscriber`に値が渡る時は、String型へ変換されて渡されていることがわかります。

## おわりに
今回は以上となります。
とりあえず、`Operator`の種類が多いのと、サンプルアプリ作ってみないと実感わかない。
ってことで、まだまだ学習が必要ですね。

## 参考文献
https://developer.apple.com/documentation/combine

https://qiita.com/Sho-heikun/items/cc28fc8a41b9932ecfb1

https://zenn.dev/usamik26/articles/combine-start-with-subject

https://zenn.dev/yorifuji/articles/ios-swift-combine