ReactiveSwift を使ってQiitaの記事一覧を表示するアプリを作った

## そもそも[ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift) とは
Reactive Extensionsでの実装をするためのライブラリで、よく比較対象にあがるのは[RxSwift](https://github.com/ReactiveX/RxSwift)です。
大きな特徴としては、HotなObservable, ColdなObservableそれぞれ異なる型を持っています。

## なぜReactiveSwiftなのか？
ちゃんとした理由があるとかではないです。今回やろうと思ったきっかけという感じになるのでご了承ください。
採用面接時に RxSwift ではなく ReactiveSwift を使用しているとの話があり、その際に先ほど挙げたHot/ColdなObservableが型から違ってHot/Coldを意識した実装が可能という話を聞き、気になっていました。安直ですが、それが今回使ってみようと思った理由です。

## 依存関係
- [QiitaAPIKit](https://github.com/ry-itto/QiitaAPIKit)

## 作ってみた結果
- [ry-itto/ReactiveSwiftSample](https://github.com/ry-itto/ReactiveSwiftSample)

## ファイル構成
```
ReactiveSwiftSample
├── AppDelegate.swift
├── Assets.xcassets
│   ├── AppIcon.appiconset
│   │   └── Contents.json
│   └── Contents.json
├── Base.lproj
│   ├── LaunchScreen.storyboard
│   └── Main.storyboard
├── DataSource.swift
├── Info.plist
├── SceneDelegate.swift
├── UITableView+Reactive.swift
├── ViewController.swift
└── ViewModel.swift
```
基本的にみてもらえると良いのは以下の4つのファイルです。

- `ViewController.swift`
- `ViewModel.swift`
- `DataSource.swift`
  - `UITableView` を使用して画面に表示するものを決める
- `UITableView+Reactive.swift`
  - `UITableView` のReactiveCocoa拡張

## コード説明
### `ViewController`
※ Storyboardを使用することを前提としています。

```swift
import UIKit

class ViewController: UITableViewController {

    let viewModel = ViewModel()
    let dataSource: DataSource

    init() {
        fatalError()
    }

    required init?(coder: NSCoder) {
        self.dataSource = DataSource(viewModel: viewModel)
        super.init(coder: coder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.reactive.setDataSource(dataSource)
        viewModel.viewDidLoadObserver.send(value: ())
    }
}
```

#### `tableView.reactive.setDataSource(dataSource)`
使用する データソースをtableViewにセットしています。よくやる

```swift
tableView.dataSource = self // selfはUITableViewDataSourceに準拠している
```
と同じものと考えてもらって大丈夫です。

#### `viewModel.viewDidLoadObserver.send(value: ())`
`viewModel` に `viewDidLoad` の状態を知らせます。 `RxSwift` の `AnyObserver#onNext` でやっているようなことと同じ感じです。

### `ViewModel`
```swift
import ReactiveSwift
import QiitaAPIKit

class ViewModel {

    typealias Elements = QiitaAPIKit.ArticleRequest.Response

    // Input
    let viewDidLoadObserver: Signal<Void, Never>.Observer

    // Output
    let items = MutableProperty<Elements>([])

    init() {
        let disposable = SerialDisposable()

        let viewDidLoadSignal = Signal<Void, Never>.pipe()
        self.viewDidLoadObserver = viewDidLoadSignal.input
        let producer = SignalProducer<Elements, Never> { observer, _ in
            QiitaAPIKit.ArticleRequest(requestQueryItem: .init(query: "iOS")).request { (result) in
                switch result {
                case .success(let response):
                    observer.send(value: response)
                case .failure:
                    observer.send(value: [])
                }
            }
        }

        disposable.inner = CompositeDisposable([
            self.items <~ viewDidLoadSignal.output
                .flatMap(.latest, { _ in producer })
                .observe(on: UIScheduler())
        ])
    }

    var numberOfSections: Int {
        return 1
    }

    func item(at indexPath: IndexPath) -> Elements.Element {
        return items.value[indexPath.row]
    }

    func numberOfItems(in section: Int) -> Int {
        return items.value.count
    }
}
```
この `ViewModel` ではAPIを叩いてデータを取得したり、テーブルビューに表示する内容について取得する プロパティ、 メソッドを定義しています。

#### [MutableProperty](http://reactivecocoa.io/reactiveswift/docs/latest/Classes/MutableProperty.html)
変更可能な値を保持し、値の変更を監視するものです。RxSwiftの `BehaviorSubject` や `BehaviorRelay` と同じようなものと捉えてもらって大丈夫だと思います。

#### `Signal<Void, Never>.pipe()`
RxSwiftで PublishSubjectを生成し、Observer, Observableに分けて使用するのと同じようなことができます。 `input` でObserver, `output` でObservableが取得できます。

#### `SignalProducer<Elements, Never>.init`
ReactiveSwiftでRxSwiftの `Observable.create` と同じようなことをするには `SignalProducer` のinitializerを使用します。クロージャの引数にObserverが渡されるので、そこに対して `send` 関数で値を流してあげます。

#### `<~`
この例では `MutableProperty` である `items` に取得した `Elements` をバインドしています。返り値に `Disposable?` が返されます。


### `DataSource`
通常DataSourceを書く時と大体同じです。 `ReactiveDataSource` に準拠し、viewModelで持っている値を取得したいのでinitialize時にviewModelを渡すようにしています。

```swift
import UIKit
import ReactiveSwift

class DataSource: NSObject, ReactiveDataSource {
    var backgroundView: SignalProducer<UIView?, Never> = .init { UIView() }

    var elementsChanged: Signal<(), Never> {
        return viewModel.items.map { _ in }.signal
    }

    var emptyView: UIView = UIView()

    var dataSource: UITableViewDataSource {
        self
    }

    var isScrollEnabled: Property<Bool> = .init(value: true)

    let viewModel: ViewModel

    init(viewModel: ViewModel) {
        self.viewModel = viewModel
    }
}

extension DataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        let item = viewModel.item(at: indexPath)
        cell.textLabel?.text = item.title

        return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.numberOfItems(in: section)
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return viewModel.numberOfSections
    }
}
```

### `UITableView+Reactive`
こちらのissue([Make it easier to work with table views / collection views #3608](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3608))からほぼそのまま持ってきました。

```swift
import ReactiveCocoa
import ReactiveSwift

protocol ReactiveDataSource: AnyObject, UITableViewDataSource {
    var backgroundView: SignalProducer<UIView?, Never> { get }
    var elementsChanged: Signal<(), Never> { get }
    var emptyView: UIView { get }
    var isScrollEnabled: Property<Bool> { get }
}

extension Reactive where Base: UITableView {
    /// Set up a reactive data source binding. Uses the binding target internally.
    func setDataSource<T: ReactiveDataSource>(_ dataSource: T) {
        self.dataSource.action(dataSource)
    }

    /// A binding target that allows you to dynamically update the data source over time.
    var dataSource: BindingTarget<ReactiveDataSource> {
        let disposable = SerialDisposable()
        lifetime += disposable
        return makeBindingTarget { base, dataSource in
            disposable.inner?.dispose()
            base.dataSource = dataSource
            disposable.inner = CompositeDisposable([
                base.reactive.backgroundView <~ dataSource.backgroundView,
                base.reactive.isScrollEnabled <~ dataSource.isScrollEnabled,
                base.reactive.reloadData <~ dataSource.elementsChanged,
            ])
            base.reloadData()
        }
    }

    var backgroundView: BindingTarget<UIView?> {
        return makeBindingTarget { $0.backgroundView = $1 }
    }
}
```

## まとめ
今回ReactiveSwiftを触ってみて正直なところこのくらいだとRxSwiftとの差異は大きくは感じませんでした。
現状は「書き方が違うもの。」の認識が強いので今後は「書き方以外の差異」を見つけて使いこなしていきたいです。