Actionを使って快適なViewModel生活を🏄

# Actionを使って快適なViewModel生活を🏄

アプリを開発してる上でAPI通信は欠かせませんが、RxSwiftを使って開発している場合
API通信で起こるエラーを正しく処理しなくてはなりません！

`Observable`の代わりに`Driver`を使うのも一手ですが、オペレーターが充実していないので困ることもあるかと思います。

#### 今回はActionというライブラリーを使ってAPI通信・エラーの処理を行う方法を書きます。

## RxSwiftで実装した場合

例えばViewModelでAPIから`Foo`の配列を取得する場合、まずは以下のように書くことができる。

```swift
class FooViewModel {
    
    let refreshTrigger = PublishSubject<Void>()
    let foos = PublishSubject<[Foo]>()
    
    init() {
        refreshTrigger
            .flatMap { APIClient.shared.responseFrom(request) }
            .bindTo(foos)
            .addDisposeBag(disposeBag)
    }
}
```

#### しかしこのコードだと以下のような問題があります

- Responseがエラーだった場合に`foos`に`.error`が流れてしまいUIのバインディングが切れてしまう。
- `foos`のプロパティーがViewController側からアクセスできてしまう。(ReadOnlyにするべき)

#### 上記の問題を解決するには以下のように変更することができます。

```swift
class FooViewModel {
    
    let refreshTrigger = PublishSubject<Void>()
    
    var foos: Observable<[Foo]> {
        return foosSubject
    }

    var error: Observable<Error> {
        return errorSubject
    }

    private let foosSubject = PublishSubject<[Foo]>()
    private let errorSubject = PublishSubject<Error>()
    
    init() {
        refreshTrigger
            .flatMap { [weak self] _ in 
                return APIClient.shared.responseFrom(request)
                    .catchError { _ in .empty } // catchErrorJustReturn([])も可能
                    .do(onError: { [weak self] error in 
                        self?.errorSubject.onNext(error)
                    })
            }
            .bindTo(foosSubject)
            .addDisposeBag(disposeBag)
    }
}
```

- Responseが`.error`の場合に`.empty`を返してストリームを維持する
- ViewModelからのアウトプットをComputed Propertyにして外部から変更できないようにする。
- エラーをViewControllerで処理できるようにする。

#### RxSwiftを使って開発している場合、ViewModelは大体こんな感じになっていると思う！

## Actionを導入する🏄

### Actionってなに？？

- Githubのレポジトリーは[こちら](https://github.com/RxSwiftCommunity/Action)


#### ReadMeにはこのように書かれています

> An action is a way to say "hey, later I'll need you to subscribe to this thing." It's actually a lot more >involved than that.
>
>Actions accept a workFactory: a closure that takes some input and produces an observable. When execute() is called, it passes its parameter to this closure and subscribes to the work.
>
> - Can only be executed while "enabled" (true if unspecified).
> - Only execute one thing at a time.
> - Aggregates next/error events across individual executions.

#### まとめるとこんな感じ

- トリガーとなる`Input`を受け取り、`workFactory`というクロージャーを実行し、`Output`を`Observable`で吐き出す。
- 一度に実行できるのは１個だけ(直列処理)
- `.next`と`.error`を別々に扱う

#### Actionの実装について

Actionの中には以下のようなプロパティーが用意されている。

```swift
public final class Action<Input, Element> {
    public typealias WorkFactory = (Input) -> Observable<Element>
    public let workFactory: WorkFactory

    public let inputs = PublishSubject<Input>()
    public let errors: Observable<ActionError>
    public let elements: Observable<Element>
}
```

#### 実行される順序

1. `inputs`を`onNext`するのをトリガーに`workFactory`が実行される
2. `workFactory`の結果が`elements`に流れてくる(これがアウトプット)
3. もし`workFactory`の中で`.error`が起きたら`errors`に流れる。


### つまりどういうことができるのか！👀

Actionを使って上のViewModelを書き換えると以下のように書くことができます！

```swift
class FooViewModel {
    
    let refreshTrigger = PublishSubject<Void>()
    
    var foos: Observable<[Foo]> {
        return refreshAction.elements
    }
    
    var error: Observable<Error> {
        return refreshAction.errors
            .flatMap { actionError -> Observable<Error> in
                if case .underlayingError(let error) = actionError {
                    return Observable.of(error)
                } else {
                    return .empty()
                }
            }
    }
    
    private let refreshAction = Action<Void, [Foo]> { _ in
        return APIClient.shared.responseFrom(request)
    }
    
    init() {
        refreshTrigger
            .bindTo(refreshAction.inputs)
            .addDisposeBag(disposeBag)
    }
}
```

- `refreshAction`の`Input`は`Void`なので`refreshTrigger`を`action`の`inputs`にバインドできる。
- `refreshTrigger`が`onNext`されたタイミングで`action`内でAPI通信が行われる。
- Responseが指定した型(この場合は`[Foo]`)で`elements`に流れてくるので、そのままComputed Propertyの値に使う。
- Errorの場合はactionの`errors`に流れてくるので、これもそのままComputed Propertyの値に使う。


### まとめ

#### Actionを使うことによって

- APIの処理を切り出して行うことができる。
- Errorが流れてしまってUIのバインディングが切れることはない。
- ViewModelからのアウトプットをComputed Propertyで定義する時に`PublishSubject`などを経由しなくて良い。

是非一度レポジトリーをのぞいてみてください🚀