【Swift × Combine】Combine入門 ①【コードあり】

# 目的
Combineとは何かを学ぶこと
「Combineを使ってミニアプリを作れる」くらいのレベルになること


# 目次

<h4>[1 : Combineとは](#1--combineとは)
<h4>[2 : 文字列の値を渡す](#2--文字列の値を渡す)
<h4>[3 : 2つ以上の受け取り先を用意する](#3--2つ以上の受け取り先を用意する)
<h4>[4 : store() : 複数のSubscriptionをまとめて保持する](#4--store--複数のsubscriptionをまとめて保持する)
<h4>[5 : assign : 値の代入](#5--assign--値の代入)


# 1 : Combineとは
Appleが提供しているSwiftのフレームワーク。
オブジェクトからオブジェクトに何かしらのイベントを伝える仕組み を提供する。
(イベントの例 : API通信,アプリ内で発生した状態変化 等)


# 2 : 文字列の値を渡す
### プログラム
```ViewController.swift
import UIKit
import Combine

// イベントの送受信を仲介
// イベントは「文字列を渡して表示させる」
let subject = PassthroughSubject<String, Never>()   // <受信する型, 返信する型>

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // イベントの受け取り
        let cancellable = subject
            .sink(receiveCompletion: { completion in
                print("Receive Completion : ", completion)
            }, receiveValue:{ value in
                print("Receive Value : ", value)
            })
        
        // イベントの送信
        subject.send("あ")
        subject.send("い")
        subject.send("う")
        subject.send("え")
        subject.send("お")
        subject.send(completion: .finished)   // イベント終了を送信
        
    }
}
```

### 実行結果
```
Receive Value :  あ
Receive Value :  い
Receive Value :  う
Receive Value :  え
Receive Value :  お
Receive Completion :  finished
```


### 解説
文字列の値を渡すという簡単なイベントの実装を行った。

手段として Combineの `PassthroughSubjectクラス`を使用している。

<br>
■ イベントの仲介として `subject` を作成

```ViewController.swift
let subject = PassthroughSubject<String, Never>()   // <受信する型, 返信する型>
```

<br>
■ イベントの送信として `subject.send("~~~")`

```ViewController.swift
// イベントの送信
subject.send("あ")
subject.send("い")
subject.send("う")
subject.send("え")
subject.send("お")
subject.send(completion: .finished)   // イベント終了を送信
```

<br>
■ イベントの受信として `subject.sink()` 
  `receiveCompleition` で実行状態(成功/失敗, .finished/.failure) を受け取る
  `receiveValue` で実行の戻り値を受け取る
  `subscription` は AnyCancellable型 である

```ViewController.swift
// イベントの受け取り
let subscription = subject
    .sink(receiveCompletion: { completion in
        print("Receive Completion : ", completion)
    }, receiveValue:{ value in
        print("Receive Value : ", value)
    })
```

:::note warn
注意
let subscription を忘れないようにしてください。
忘れた場合、イベントの受け取りを保持できません。

理由
sinkで指定した受信処理を有効にするためには、sinkの戻り値を破棄せずに保持し続ける必要があるからです。
subscription という変数に保持し続ける必要があります。
:::


# 3 : 2つ以上の受け取り先を用意する
### プログラム
```ViewController.swift
import UIKit
import Combine

// イベントの送受信を仲介
// イベントは「文字列を渡して表示させる」というもの
public let subject = PassthroughSubject<String, Never>()   // <受信する型, 返信する型>

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // イベントの送信
        let receiver = Receiver()
        subject.send("あ")
        subject.send("い")
        receiver.subscription1.cancel()   // subscription1 を中止する
        subject.send("う")
        subject.send("え")
        subject.send("お")
        subject.send(completion: .finished)   // イベント終了を送信
        
    }
}

// 受け取り先を2つ作成
// final : 継承を禁止する
final class Receiver {
    // 受け取りを2つ用意
    let subscription1: AnyCancellable
    let subscription2: AnyCancellable
    
    init() {
        
        // 1つ目の受け取り
        subscription1 = subject
            .sink{ value in
                print("[1] subscription : ", value)
            }
        
        // 2つ目の受け取り
        subscription2 = subject
            .sink{ value in
                print("[2] subscription : ", value)
            }
        
    }
}
```

### 実行結果
```
[1] subscription :  あ
[2] subscription :  あ
[1] subscription :  い
(本来は改行なし)           ← subscription1をキャンセルする
[2] subscription :  い
[2] subscription :  う
[2] subscription :  え
[2] subscription :  お
```


### 解説

Receiver() に複数のsubscriptionを作成している。

`subject.send("~~~")`を行うと、複数のsubscription全てに向けて送信が実行される。

`receiver.subscription1.cancel()` のように、subscriptionを指定して送信をキャンセルすることが可能。
→ 本当にキャンセルできているかは、実行結果から確認できる


# 4 : store() : 複数のSubscriptionをまとめて保持する
### プログラム
```ViewController.swift
import UIKit
import Combine

// イベントの送受信を仲介
// イベントは「文字列を渡して表示させる」というもの
public let subject = PassthroughSubject<String, Never>()   // <受信する型, 返信する型>

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // イベントの送信
        let receiver = Receiver()
        subject.send("あ")
        subject.send("い")
        subject.send("う")
        subject.send("え")
        subject.send("お")
        subject.send(completion: .finished)   // イベント終了を送信
        
    }
}

// 受け取り先を2つ作成
// final : 継承を禁止する
final class Receiver {
    // 受け取りを2つ用意
    //let subscription1: AnyCancellable
    //let subscription2: AnyCancellable
    var subscriptions = Set<AnyCancellable>()   // → 2つのsubscriptionを 1つにまとめるための subscription
    
    init() {
        // 1つ目の受け取り
        //subscription1 = subject
        // 1つのsubscriptionにまとめる場合は subjectを変数で保持する必要なし
        subject
            .sink{ value in
                print("[1] subscription : ", value)
            }
            .store(in: &subscriptions)   // 1つのsubscriptionにまとめる
        
        // 2つ目の受け取り
        //subscription2 = subject
        subject
            .sink{ value in
                print("[2] subscription : ", value)
            }
            .store(in: &subscriptions)   // 1つのsubscriptionにまとめる
    }
}
```


### 実行結果
```
[1] subscription :  あ
[2] subscription :  あ
[1] subscription :  い
[2] subscription :  い
[1] subscription :  う
[2] subscription :  う
[1] subscription :  え
[2] subscription :  え
[1] subscription :  お
[2] subscription :  お
```

### 解説
`var subscriptions = Set<AnyCancellable>()` によって 複数のsubscriptionを1つにまとめることができる。

Set へ複数のsubscriptionを入れるためには、`.store(in: &(Set先のsubscription))`が必要

sink の後に store を呼べば良い


# 5 : assign : 値の代入
### プログラム
```ViewController.swift
import UIKit
import Combine

// イベントの送受信を仲介
// イベントは「文字列を渡して表示させる」というもの
public let subject = PassthroughSubject<String, Never>()   // <受信する型, 返信する型>

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // イベントの送信
        let receiver = Receiver()
        subject.send("あ")
        subject.send("い")
        subject.send("う")
        subject.send("え")
        subject.send("お")
    }

}

final class Receiver {
    var subscriptions = Set<AnyCancellable>()
    
    init() {
        // .assign を使い,受信結果をクラスの変数に代入する
        subject
            .assign(to: \.value, on: SomeObject())
            .store(in: &subscriptions)
    }
}

// 値が代入されるか確認するためにクラス
// valueに値が代入される
final class SomeObject {
    var value: String = "" {
        didSet {
            print("didSet value : ", value)
        }
    }
}
```

### 実行結果
```
didSet value :  あ
didSet value :  い
didSet value :  う
didSet value :  え
didSet value :  お
```


### 解説
`.assign(to: \.(代入先の変数名), on: (代入先のクラス名))` を使い、クラスの変数へ代入をおこなっている
→ assign の使い方は、sinkと似ている

:::note info
assign の利用条件

エラー型がNever のイベント(エラーが発生しないイベント)であること
:::

:::note warn
注意 : assign(to: \.(代入先の変数名), on: (代入先のクラス名)) について

to で指定するプロパティは書き込み可能なもの
on で指定するオブジェクトは参照型(classやstructなど)
である必要がある。
:::


# 参考資料

https://www.amazon.co.jp/Combine%E3%82%92%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86-%E5%AE%87%E4%BD%90%E8%A6%8B-%E5%85%AC%E8%BC%94-ebook/dp/B091K6Z19C

<img width = 300 src= "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764557/8d5e8c6e-4b96-8423-02d7-ed4f0c2d8c91.png" >