こんな時どう書くの？初級者向けSwift Combineチートシート

この記事は、ニフティグループ Advent Calendar 2021 13日目の記事です。

[ニフティライフスタイル](https://niftylifestyle.co.jp/)所属エンジニアから3人目の投稿です！
iOSアプリエンジニアなのでiOSアプリについての記事を投稿します。

# はじめに
WWDC 2019にてCombineが発表されて早2年、ようやくiOS12のサポートが切れて自分のプロダクトにCombineが導入できる！って方もいるのではないでしょうか...
私が携わっている[ニフティ不動産アプリ](https://myhome.nifty.com/apps/)もついにiOS13以上サポートになり、今年初めてCombineをプロダクトコードに導入しました。

DelegateやClosureを駆使して実装してきた私にとって、Combineの宣言的な概念や実装を理解するのは結構大変でした。
今回は自分の復習も兼ねて、Combineをざっと学んでみたけどこんな時にどんな文法を使えば良いんだっけ？というときに使えるCombineチートシートを作成しました！


# とりあえず何かをCombineを使って流してみたい
公式で用意されたPublisherを使えます。

## Just
値を即時に1回だけ流すことができます。エラーは流せません。
ダミーの値を流して動作を確認したい時などに使えます。

```swift
Just("Taro")
    .map { name in
        "Hello, \(name)!"
    }
    .sink { message in
        print(message)
    }.store(in: &cancellables)
```
```
Hello, Taro!
```


## Future
値かエラーを非同期的に1回だけ流すことができます。
クロージャで書かれているAPIをCombineで流したい時など、返却値をPublisherに変換するときに、ラップして使うことができます。
注意としてはインスタンスを生成した時点で`Future`の中を実行してしまうので、`Deferred` で囲うことでSubscribeした時に初めて実行されるようになります。

```swift
func testPublisher() -> AnyPublisher<Int, Never> {
    Deferred {
        Future<Int, Never> { (promise) in
            // 非同期処理
            DispatchQueue.global(qos: .userInitiated).async {
                print(Thread.isMainThread)
                promise(.success(1))
            }
        }
    }.eraseToAnyPublisher()
}

testPublisher().sink { int in
    print(int)
}.store(in: &cancellables)
```

```
false
1
```

## Empty
「Never」パブリッシャーを作成し、終了だけを流します。
Publisherを生成しなければいけないが、値もエラーも流したくないときに利用します。
私はテストコードなどでとりあえず何かを流さないといけなかったり、メソッド内が未実装のときにビルドエラーにならないように仮で実装するときに利用します。

```swift
Empty().eraseToAnyPublisher()
```

# 定期的に値を流し続けたい
## Subjects
Subscriber（値を受け取る側）へ値を流すことができます。出力終了の通知を送るまで、`send`を呼ぶことで継続的に値を流し続けることができます。
Subjectsには2種類あります。

### CurrentValueSubject
最後に出力した値を一つ保持し、値を流すたびに保持する値も更新されます。
Subscriber（値を受け取る側）が購読を開始したときに初期値が欲しいときはこちらを利用します。

```swift
let subject = CurrentValueSubject<Int, Never>(0)
subject.sink { int in
    print(int)
}.store(in: &cancellables)
subject.send(1)
subject.send(2)
subject.send(3)
```

```
0
1
2
3
```

### PassthroughSubject
PassthroughSubjectは値を保持せず値を流します。

```swift
let subject = PassthroughSubject<Int, Never>()
subject.sink { int in
    print(int)
}.store(in: &cancellables)
subject.send(1)
subject.send(2)
subject.send(3)
```

```
1
2
3
```

# プロパティをPublisherにしたい
## @Published
`Property Wrappers`という機能を使って`@Published`を先頭につけることによって、プロパティをPublisherにすることができます。

```swift
class Human {
    @Published var name: String = "Taro"
}
let human = Human()
human.$name
    .sink { name in
        print("Hello, \(name)!")
    }.store(in: &cancellables)

human.name = "Jiro"
human.name = "Saburo"
```

```
Hello, Taro!
Hello, Jiro!
Hello, Saburo!
```


# 複数のPublisherをまとめて受け取りたい
さまざまな書き方がありますが、以下の記事がとてもわかりやすくて参考になったのでリンクを紹介します。
[【Swift】CombineフレームワークのCombining Operators(Publisherを結合するOperator)の動きを確認する](https://qiita.com/shiz/items/cff44fe801243b8a5496)
私は `merge`や`combineLatest`をよく使います。


# まとめ
まだまだ他にもCombineの文法はたくさんありますが、私のプロダクトでよく使ったものをまとめました。
これからCombineを学ぶ人にとって参考になったら嬉しいです！

# 参考
* [【iOS】Combineフレームワークまとめ](https://qiita.com/shiz/items/5efac86479db77a52ccc)
* [ちょっとしたことにCombineを使う -Combine初心者向け-](https://zenn.dev/ryotapoi/articles/7dba6606f8e80b8f65d8)
* [[Swift] はじめてのCombine | Apple製の非同期フレームワークを使ってみよう](https://dev.classmethod.jp/articles/swift-combine-framework-for-beginners/)