【Swift】フロントエンジニアがSwiftのリアクティブプログラミングを始めてみた話（RxJSと比較付）

## **はじめに**
普段AngularでRxJSを利用しながらコーディングしているSwift初心者の筆者が
Apple製の非同期フレームワークである、 **[Combine](https://developer.apple.com/documentation/combine)** を利用してプログラミングしてみた記事になります。

https://developer.apple.com/documentation/combine

どなたかの参考になれば幸いです。

## **Combineって何？**
iOS13 から利用できるようになったフレームワークです。
※ iOS 13以降に使えるフレームワークなのでiOS 12以下は使えないことにご注意ください。


### **Combineの大事な3つの要素**

* **Publishers**: イベントの発行者
* **Subscribers**: イベントの購読者です　
* **Operators**: 流れてくる値を加工することができます　

新しい言語で新しい概念が出てくると中々理解しづらい部分があるため
慣れ親しんだRxJsとの対比表を書いて整理してみます。

**CombineとRxJSとの対比表**

||Combine|RxJS|
|--|--|--|
|イベントの発行者|Publishers|Observable|
|イベントの購読者|Subscribers|Observer|
|流れてくる値の加工|Operators|Operator|

RxJsと対比してみて、なんとなく理解が進んだところで実際に簡単なサンプルコードを書いてみます。


## Publisherを作る （Future)

Futureとは非同期で値を返すことが可能なPublisherです。

https://developer.apple.com/documentation/combine/publisher

値を 1つ 発行してfinish or failsのどちらかをすることができます。

値は Genericsになっていますので、 左辺に返したい値の型、右辺にエラーの型を定義する必要があります。
エラーの型は Error型 でも、自分で定義したErrorプロトコルに準拠した型でもいいです。

```swift
enum MyError: Error {
	case BadRequestError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
	case NotFoundError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
	case SystemError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
}


func testFuture(v: Bool) -> Future<String, MyError> {
	return Future { promise in
		if (v) {
			print("hello in test")
			promise(.success("hello"))
		} else {
			promise(.failure(AnError.NotFoundError("", "???", nil)))
		}
	}
}

testFuture(true)

// 出力
hello in test
```

注意点としては、処理が実行されるタイミングが、初めてSubscribeされたタイミングではなく
**Futureインスタンスが生成されたタイミング** になります。
※ RxJsでいうところの .subscribeしたタイミングで実行されるわけではない

そのため上記サンプルを見ていただいたら分かるとおり、 Future型の 関数を購読（Subscribers）していない
ですが、関数内の print()が実行され "hello in test" が出力されていることがわかります。


## **Futureを遅延実行させる(Deferred)**

先ほどのFutureの場合、Subscribeをしていないのに処理が実行されてしまい
思わぬ動作をしてしまう可能性もあります。

そのため購読したタイミングで処理を実行させる **Deferred** を活用する方法があります。

https://developer.apple.com/documentation/combine/deferred

```swift
enum MyError: Error {
	case BadRequestError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
	case NotFoundError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
	case SystemError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
}

func testDeffered(v: Bool) -> Deferred<Future<String, AnError>> {
		return Deferred {
			Future { promise in
				if (v) {
					print("hello in test")
					promise(.success("hello"))
				} else {
					promise(.failure(AnError.NotFoundError("", "???", nil)))
				}
			}
		}
	}

testDeffered(true)

// 出力
なし
```

先ほどのFutureと異なり、購読していないため Publisher内の print() が実行されていない事がわかります。

実際に購読してみた場合の動きとコードは以下になります。 (購読については下で紹介)

```swift
enum MyError: Error {
	case BadRequestError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
	case NotFoundError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
	case SystemError(_ value: Any, _ cause: String, _ stackTrace: String? = nil)
}

func testDeffered(v: Bool) -> Deferred<Future<String, AnError>> {
		return Deferred {
			Future { promise in
				if (v) {
					print("hello in test")
					promise(.success("hello"))
				} else {
					promise(.failure(AnError.NotFoundError("", "???", nil)))
				}
			}
		}
	}

testDeffered(true).sink(receiveCompletion: { completion in
	switch completion {
	  case .finished:
		print("finished")
	  case .failure(let error):
		print("error \(error)")
	}
}, receiveValue: { value in
	print(value)
})


// 出力
hello in test
hello
finished
```

Deferredの動きをRxJSで書いてみた場合は以下が近いような感じに近いと思います。

```.ts
const helloObservable = of('hello');
helloObservable.subscribe(res => console.log(res)); // subscribeしたタイミングで実行される
```

また、FutureやDeferred以外にも以下のようなPublisherがあるので、調べてみてもいいかもしれません。

* Empty
* Fail
* Record

**Conforming Types**

https://developer.apple.com/documentation/combine/publisher



## **購読する(Subscribers）**

Publisherを購読するつ１つの方法として、 Deferredで紹介した **sink** が使えます。

```swift
func test() throws {
	Future<String, Error> { promise in
		promise(.success("hello"))
	}.sink(receiveCompletion: { completion in
		switch completion {
		case .finished:
			break
		case .failure(let error):
			print("error \(error)")
		}
	}, receiveValue: { value in
		print("value \(value)")
	})
}

test()

// 出力
hello in test
hello
finished
```

RxJSでいうところの Observableを受け取ってsubscribe()を呼び出す処理にあたると思います。

## **流れてくる値の加工（Operators）**

Publisherで流れてきた値を 購読する前に値を加工して渡したい場合は
**map** などのOperatorsを利用します。

※ RxJSでいうmapやflatMapもCombineにはありますので似たような感覚で使えるかなと思います！

```swift
func testOperators() throws {
	Future<Int, Error> { promise in
		promise(.success(1))
	}.map { x in x * 2
	}.sink(receiveCompletion: { completion in
		switch completion {
		case .finished:
			break
		case .failure(let error):
			print("error \(error)")
		}
	}, receiveValue: { value in
		print("value \(value)")
	})
}

// 出力
value 2
```
RxJSと同じく、map以外にもさまざまなOperatorsが標準で用意されているため調べてみてもいいかもしれません。


## **おわりに**
Swiftの非同期周りの処理を書く際に、HydraやRxSwiftなどサードパーティーのライブラリを使うと
言語やOSのバージョンが上がった際にメンテナンスしんどそうだなぁと思っていたのですが
新しめのOSでしか利用できないものの、標準で用意されていて Swiftいいなと思いました。

Rx系のライブラリを触った事がある方なら簡単な処理ならすんなりと書けるところもグッドです。

ぜひ積極的に活用していければなと思います。