Alamofire + RxSwiftを利用した一覧データ取得のシンプルなアーキテクチャ実装のサンプル

## はじめに
今回は[前回](https://qiita.com/alyousecond/items/540536f97b24f18e7360)に続き、AlamofireとRxSwiftを利用して、ネットワーク経由で一覧データを取得する際に、よく必要となる下記の処理を含めたシンプルなアーキテクチャでの実装例をご紹介します。

- データ取得中はIndicatorを表示させる（前回と同じ）
- エラーが発生した場合はバナーを表示させる（前回と同じ）
- **PullToRefreshで一覧の更新させる（今回追加**）
- **下までスクロールしたら、続きのデータを取得して一覧に追加して表示させる（今回追加**）
- **下記のユーザインタラクションを一覧画面に反映させる（今回追加**）
 - 「いいね」ボタンをクリック
 - 既読/未読（Cellのタップ）
  
*※ただし、データの更新処理は今回実装していないため、ユーザインタラクションで更新されたデータは、一覧を更新すると元に戻ってしまいます。*

<img width="250" alt="Simulator Screen Shot - iPhone 8 - 2018-06-26 at 10.18.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/262203/4fe4c802-2a48-daed-b2d4-359220550488.png"> <img width="250" alt="Simulator Screen Shot - iPhone 8 - 2018-06-26 at 10.18.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/262203/4d800cdc-d1a5-ac67-f5f4-81fcadcf5889.png"> <img width="250" alt="Simulator Screen Shot - iPhone 8 - 2018-06-26 at 10.19.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/262203/f188d043-9ed8-2330-7dea-49eadc03a864.png">


## アーキテクチャ
アーキテクチャの構成は[前回](https://qiita.com/alyousecond/items/540536f97b24f18e7360#アーキテクチャ)と基本的には同じです。

## 今回のサンプルで利用しているサービス・ライブラリのご紹介
前回からライブラリの追加は変更ありません。
サービスはQiitaの投稿一覧を取得するAPIを利用しています。

### Qiita https://qiita.com/api/v2/docs#get-apiv2items
投稿の一覧を作成日時の降順で返します。

**※[重要] QiitaのAPIリクエストは1時間に60回までの制限がございますので、それを超えるとエラーになりますのでご注意ください。**

## 作成したオブジェクトの説明
今回は前回より、だいぶ盛りだくさんになっています。
順を追って説明していきます。

### Global/RequestState(Struct)
前回はデータを取得中かどうかの状態しか管理していませんでしたが、今回はリクエストに応じた処理を行うために`RequestState`としてStructを定義。
- `Equatable`プロトコルに準拠

### Extension/Bool+Ex
これは好みの問題もあるかと思いますが、Bool値の否定を!で判定するのではなく.reverseとして表現できるようにしたものです。

```
if cell.isSubscribe.reverse {
・・・
}
```

### Protocol/State+Has
今回はViewControllerの共通処理をプロトコルとして定義しており、それらのプロトコルを利用する上で必要なStateのプロパティをプロトコルとして定義しています。

**HasFetching(Protocol)** *今回は未使用*
データ取得時にインジケーターを表示させるために必要なStateプロパティ。

**HasError(Protocol)**
エラー時にNotificationバナーを表示させる際に必要なStateプロパティ。

**HasRequestState(Protocol)**
データ取得、PullToRefreshの処理を行う場合に必要なStateプロパティ。

**HasPagingState(Protocol)**
データを指定した件数づつ取得して、下部までスクロールしたら次の件数分取得する処理に必要なStateプロパティ。

elementsには`associatedtype`で定義した`Identifiable`の配列とすることにより、実態はIdentifiableに準拠したデータの配列とすることが可能になる。

*プロトコル側*

```
protocol HasPagingState: HasRequestState {
    associatedtype T: Identifiable
    var elements: [T] { get }
    ・・・・
}
```

*実装側*

```
struct ListState: HasError, HasRequestState, HasPagingState {
    var elements: [QiitaItemElement] = []
    ・・・・
}

struct QiitaItemElement: Identifiable {
　　　　・・・・
}
```


### Protocol/ViewController+Able
今まではViewControllerでの共通処理はBaseViewControllerなどで実装してきたこともあったかと思います。今回はそれらの共通処理をプロトコルとして定義。

**FetchingPresentable(Protocol)**　*今回は未使用*

- データ取得時にインジケーターを表示させるために必要なStateの監視処理。
- 前回はViewControllerに記述していた処理をプロトコルとして定義。
- バインドするstateはジェネリクスで指定したプロトコル（今回は`HasFetching`)に準拠したもの。
- `HasFetching`に準拠したStateと組み合わせ利用。

**RefreshingPresentable(Protocol)**

- PullToRefreshでデータを取得するために必要なStateの監視処理。
- 利用する場合は、`UIRefreshControl`とその対象となる`UIScrollView`を継承したクラスが必要。
- バインドするstateはジェネリクスで指定したプロトコル（`HasRequestState`)に準拠したもの。
- `HasRequestState`に準拠したStateと組み合わせて利用

```
func bindRefreshing<T: HasRequestState>(state: Observable<T>, disposeBag: DisposeBag)
```

- Classでしか利用しないためにProtocolにclass指定

```
protocol RefreshingPresentable: class {
　　　　・・・・
}
```

**PagingPresentable(Protocol)**

- データを指定した件数づつ取得して、下部までスクロールしたら次の件数分取得する処理に必要なStateの監視処理。
- `HasPagingState`に準拠したStateと組み合わせて利用。
- `IndicatorFooterViewProtocol`に準拠した`UIView`が必要。
- インジゲーターとして表示する`footView`は`associatedtype`で定義した`IndicatorFooterViewProtocol`を指定。

プロトコル側

```
protocol PagingPresentable: class {
    associatedtype T: IndicatorFooterViewProtocol
    var footerView: T { get }
}
```

実装側

```
class ListViewController: UIViewController, RefreshingPresentable, PagingPresentable, ErrorNotificationPresentable {
    var footerView = IndicatorFooterView(frame: CGRect(x: 0, y: 0, width:    UIScreen.main.bounds.width, height: 44)) // PagingPresentable
　　・・・・
}

class IndicatorFooterView: UIView, IndicatorFooterViewProtocol {
　　・・・・
}
```

**IndicatorFooterViewProtocol(Protocol)**
- `PagingPresentable`を利用した際に、最下部に表示するインジケーター兼最終レコードまで取得した際に表示させるFooter表示ビュー。
- `PagingPresentable`プトロコルを利用する際には必須。

```
protocol IndicatorFooterViewProtocol {
    func startAnimating() // インジケーターの開始
    func stopAnimating() // インジケーターの開始
    func toggleEndIndicator(isShowing: Bool)　 // インジゲーターと終了の表示切り替え
}
```

**ErrorNotificationPresentable(Protocol)**
- エラーが発生した場合に`NotificationBanner`を表示させるために必要なStateの監視処理。
- `HasError`に準拠したStateと組み合わせて利用。
- `NotificationBanner`を利用することが前提。

### Protocol/Element+Able
- 前回は取得したEntityをViewの表示に利用してきたが、ユーザのインタラクション管理を行うためにEntityとは別に表示するためのモデルとしてElementを定義。

**Identifiable(Protocol)**
- Elementに識別子を持たせるためのプロトコル。
- `identifier`というプロパティのみ定義。

```
protocol Identifiable {
    var identifier: String { get }
}
```

### Common/AppBuilder(Struct)
- `UIViewController`のコンストラクタを作成・取得するための処理。
- `UIViewController`に必要なPresenterやRepositoryをイミュータブルなデータとして初期化時に渡すことが可能。トレードオフとしてStoryBoardを利用した画面作成はできなくなる。
- StoryBoardを利用したい場合は、`UIViewController`のインスタンスを作成したあとに、Presenter、Repositoryなどを注入する。ただし、UIViewController側ではPresenterやRepositoryを強制アンラップなどする必要あり。

Builder側の実装

```
func buildListView() ->ListViewController  {
    let repository = ListRepository()
    let presenter = ListPresenter(repository: repository)
    let viewController = ListViewController(presenter: presenter)
    return viewController
}
```

UIViewContoller側の実装

```
class ListViewController: UIViewController, ・・・ {
    private let presenter: ListPresenterProtocol

    init(presenter: ListPresenterProtocol) {
        self.presenter = presenter
        super.init(nibName: nil, bundle: nil)
    }
}
```
UIViewController側での`presenter`の定義は`ListPresenterProtocol`とすることにより、スケルトンやテストモジュールとの入れ替えが可能になり、`ListPresenter`との依存性をなくすことが可能になる。


### ListViewController(Class)
- `RefreshingPresentable`、`PagingPresentable`、`ErrorNotificationPresentable`に準拠

各プロトコルのプロパティの定義

```
let refreshControl = UIRefreshControl() // RefreshingPresentable
var refreshControlView: UIScrollView { return tableView  } // RefreshingPresentable
var bannerQueue: [NotificationBanner] = [] // ErrorNotificationPresentable
let footerView = IndicatorFooterView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44)) // PagingPresentable
let tableView = UITableView(frame: .zero, style: .plain) // PagingPresentable
```

各プロトコルのバインディング処理

```
bindRefreshing(state: viewState, disposeBag: disposeBag) // RefreshingPresentable
bindPaging(state: viewState, disposeBag: disposeBag) // PagingPresentable
bindErrorNotification(state: viewState, disposeBag: disposeBag) // ErrorNotificationPresentable
```

`RefreshControl.addTarget`の`#selector`はExtension側で実装できないためにViewController側で定義

```
refreshControl.addTarget(self, action: #selector(refresh), for: UIControlEvents.valueChanged) // Required for refresh controll
```

Cellに配置した部品で発生したユーザインタラクションのイベントを処理するために、Cellに`QiitaItemElement`を設定し

```
cell.bind(element: element)
```

`ListViewCell`は`Identifiable`プロトコルに準拠させ、Cellごとにバインドしたデータの`identifier`を保持して、Cellでイベントが発生した際に識別子として利用。

```
self?.presenter.toggleLike(with: cell.identifier)
```

また、CellのボタンイベントをSubscribeする際には、複数回SubscribeしないようにCell側で管理。

```
if cell.isSubscribe.reverse {
    cell.likeButton.rx.tap
        .subscribe(onNext: { [weak self] in self?.presenter.toggleLike(with: cell.identifier) })
        .disposed(by: cell.disposeBag)
    cell.isSubscribe = true
}
```

Cellの選択時のイベント処理はいたってシンプル

```
tableView.rx
    .modelSelected(QiitaItemElement.self)
    .subscribe(onNext: { [weak self] element in self?.presenter.markAsRead(with: element.identifier) })
    .disposed(by: disposeBag)
```

### ListRepository(Struct)
- `ListRepositoryProtocol`を定義した上での実装。
- プロトコルとして定義することにより、`ListPresenter`との依存性をプロトコルに限定でき、テストやスケルトン用のコンストラクタが作成可能になる。

### ListState(Struct)
- `HasError`、`HasRequestState`、`HasPagingState`のプロトコルに準拠。
- `renewRequestStart`、`renewRefreshStart`、`renewPagingStart`などもプロトコルのExtensionとして実装する余地がある。（未対応）
- stateの更新は既存のデータをコピーして書き換えを行う。

```
func renewReadItem(selectItem: QiitaItemElement) -> ListState {
    var newState = self
    ・・・・
    return newState
}
```

### ListPresenter(Struct)
- `ListPresenterProtocol`を定義した上での実装。(理由はListRepositoryと同じ）

```
struct ListPresenter: ListPresenterProtocol {
    var repository: ListRepositoryProtocol
    init(repository: ListRepositoryProtocol) {
        self.repository = repository
    }
}
```

### QiitaItemEntity、QiitaUserEntity(Struct)
- Mappableプロトコルに準拠。
- 取得したJSONデータをオブジェクトにマッピングするための加工処理を行います。

### QiitaItemElement、QiitaUserElement(Struct)
- Identifiableプロトコルに準拠。
- Viewの表示やユーザインタラクションに必要な各データのプロパティを管理。
- 状態に応じた色などをcomputed propertyとして定義。

```
enum ReadStatus {
    case read
    case unread
    var color: UIColor {
        switch self {
        case .read: return UIColor.white
        case .unread: return UIColor(red: 255/255, green: 186/255, blue: 82/256, alpha: 0.2)
        }
    }
}
```

### ListViewCell(Class)
- 一覧表示用のカスタムCell。
- Identifiableプロトコルに準拠。

```
class ListViewCell: UITableViewCell, Identifiable {
　・・・
}
```

### IndicatorFooterView(Class)
- IndicatorFooterViewProtocolプロトコルに準拠。
- PagingPresentable用のFooterに表示させるためのView。

今回はViewControllerの共通処理にプロトコルを利用しています。
これらを利用することにより、スーパークラスに共通の処理を実装することなく共通の処理を実装させることが可能になります。

## ソースコード
完成したソースは下記にあります。
https://github.com/alyousecond/AlamofireExample
git cloneしたあとcarthage updateしてください。

```
$ git clone https://github.com/alyousecond/AlamofireExample.git
$ cd AlamofireExample
$ carthage update --platform ios
```