筋肉.swiftアプリをMVPパターンで実装(しようと)してみて感じたこと

11月29日に[筋肉.swift](https://kinniku-swift.connpass.com/event/69438/)という所謂普通のiOS勉強会を主催しまして、イベントを盛り上げるためにiOSアプリをリリースしました。

https://itunes.apple.com/jp/app/id1312864123?mt=8

今回、作るに当たってある程度テーマを設けたいなと思いまして、

・**Cloud Firestoreを使ってみる**
・**MVPパターンで実装してみる**

という２つのテーマに設定しました。

そのうち今回は、後者の**MVPパターンで実装してみる**をやってみて感じたメリット、疑問に思ったことなどを書いていきたいと思います。

ちなみにソースコードは以下にあります。

https://github.com/kboy-silvergym/KinnikuSwift

## MVPパターンとは

<img width="703" alt="スクリーンショット 2017-12-01 0.11.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/111366/063cdca0-733f-ca3c-f7ee-f01bb68273b3.png">
出典：https://speakerdeck.com/amacou/mvpfalseyounamofalsewotimuniti-an-sitahua?slide=23

<img width="604" alt="スクリーンショット 2017-12-01 0.11.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/111366/b456ce3f-ee41-8757-d3d1-16eaa2c8710e.png">
出典：https://speakerdeck.com/amacou/mvpfalseyounamofalsewotimuniti-an-sitahua?slide=29

非常にわかりやすかったので上の[スライド](https://speakerdeck.com/amacou/mvpfalseyounamofalsewotimuniti-an-sitahua)をシェアさせていただきました。

今回、MVPを採用する目的としては、**ViewControllerがでかくなるのを解消できるのか？** を個人的に検証するため。サンプルじゃなくて実際に動かすことを目的としたアプリでどうなるのか検証したかったため、筋肉.swiftアプリを使ってやってみました。

以下、実際に実装して見て、**メリットを感じた例、意味なくね？と思った例、これでいいのか？と疑問に思った例**をあげていきたいと思います。もし知見のある方がいましたらご意見お願いしたいです。

## メリットを感じた例

登壇してくれるスピーカー一覧を表示するSpekerListViewControllerを実装するに当たってPresenterはViewControllerを薄くすることに貢献してくれました。

`getSpeakerRealtime`、`sort`、`getVoteAlert`という３つのメソッドがあり、これをこのままViewControllerに書くと60行くらいは占有します。ViewControllerがとてもでかくなって見にくくなるところをPresenterが請け負うことで回避できたと思いました。

https://github.com/kboy-silvergym/KinnikuSwift/blob/master/kinniku-swift/View/Scene/SpeakerList/SpeakerListPresenter.swift

## 意味なくね？と思った例

自分で、**MVPにしてやる！**というテーマに決めたので意味ない気がしても無理やりPresenterを作ることにしました。

```swift:FeedPresenter.swift
import UIKit

class FeedPresenter {
    
    func getTweet(_ completion: @escaping ([Tweet]) -> Void){
        TwitterAPI.getTweet({ results in
            guard let tweets = results?.tweets else {
                completion([])
                return
            }
            completion(tweets)
        })
    }
    
    func showTweetComposer(_ vc: UIViewController){
        TwitterAPI.showTweetComposer(fromVC: vc, completion: { result in
            switch result {
            case .done:
                break
            case .cancelled:
                break
            }
        })
    }
}
```
https://github.com/kboy-silvergym/KinnikuSwift/blob/master/kinniku-swift/View/Scene/Feed/FeedPresenter.swift

呼び出し側のViewControllerのメソッドが以下。

```swift:FeedViewController.swift
    func getTweet(){
        presenter.getTweet({ tweets in
            self.tweets = tweets
            self.refreshControl?.endRefreshing()
            self.tableView.reloadData()
        })
    }
    
    @IBAction func addButtonTapped(_ sender: Any) {
        presenter.showTweetComposer(self)
    }
```
https://github.com/kboy-silvergym/KinnikuSwift/blob/master/kinniku-swift/View/Scene/Feed/FeedViewController.swift

Viewから**Modelを直接触らない**というルールを守るためにTwitterAPIクラスをPresenterにおくようにしてみました。しかし、これでは**Modelを直接触らない**ルールが守られているだけで、ViewControllerを小さくするという目的が達成されていません。伝言ゲームをしているだけ。

これが意味なくね？と思ったポイントです。

## 疑問に思った例

さらに、同じ部分のFeedPresenterまわりのMVP実装で疑問に思うことがあります。

以下にFeedViewControllerを全て貼ります。

```swift:FeedViewController

import UIKit
import InteractiveSideMenu

class FeedViewController: UIViewController {
    let presenter = FeedPresenter()
    
    @IBOutlet weak var tableView: UITableView!
    var refreshControl: UIRefreshControl?
    
    var tweets: [Tweet] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
        tableView.tableFooterView = UIView()
        
        refreshControl = UIRefreshControl()
        refreshControl?.addTarget(self, action: #selector(type(of: self).refresh), for: .valueChanged)
        tableView.refreshControl = refreshControl
        
        let nib = UINib(nibName: String(describing: TweetCell.self), bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: String(describing: TweetCell.self))
        
        getTweet()
    }
    
    @objc func refresh(){
        getTweet()
    }
    
    func getTweet(){
        presenter.getTweet({ tweets in
            self.tweets = tweets
            self.refreshControl?.endRefreshing()
            self.tableView.reloadData()
        })
    }
    
    @IBAction func addButtonTapped(_ sender: Any) {
        presenter.showTweetComposer(self)
    }
    
    @IBAction func menuButtonTapped(_ sender: Any) {
        if let navigationViewController = self.navigationController as? SideMenuItemContent {
            navigationViewController.showSideMenu()
        }
    }
}

// MARK: - <#UITableViewDataSource#>
extension FeedViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tweets.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TweetCell.self), for: indexPath) as! TweetCell
        cell.tweet = tweets[indexPath.row]
        return cell
    }
}
```

疑問は、`var tweets: [Tweet] = []`はFeedViewControllerのメンバ変数にあっていいのか？という点です。ドメインモデルを管理するのはもしかしてPresenterであるべきなのではないか！？という懸念があります。

しかし、上記にある通り、`FeedViewController`は`UITableViewDataSource`のメソッドを持っていて、cellにtweetをセットするためにViewControllerにtweetはメンバ変数としてあって欲しいところです。それとも

```swift
cell.tweet = presenter.tweet[indexPath.row]
```

みたいになるべき？

なにかしっくり来ない気がします。

`UITableViewDataSource`はView/ViewController側の責務としてあるべきだと思うので、`UITableViewDataSource`をPresenterが持つというのもルール違反な気がしています。そもそもそしたら複雑になりそうですし。。。

疑問点以上です。。

## 追記

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">例えばこのVCとかで、photoContentsをpresenterに置いたりするべきなのかな？ってことを僕は考えていました。けど結局VCに置いた方がいい感じだったので僕も <a href="https://twitter.com/fumiyasac?ref_src=twsrc%5Etfw">@fumiyasac</a> さんと同じようにphotoContents的なモデルをVCのメンバ変数に置いて実装しています。<a href="https://t.co/8qTNkG1t70">https://t.co/8qTNkG1t70</a></p>&mdash; K-BOY@筋肉.swift (@kboy_silvergym) <a href="https://twitter.com/kboy_silvergym/status/936293301832925184?ref_src=twsrc%5Etfw">2017年11月30日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Twitterでコメントいただいた @fumiyasac@github さんの例でもtableViewの要素を示すphotoContents的なモデルは、ViewControllerのメンバ変数に置いていました。

やはりこれがtableViewにおけるMVPのベストプラクティスかもしれない。

## まとめ

ということで、後半はstackOverFlowの質問みたいになってしまいましたが、**ViewControllerを薄くしたい！**という目的においてMVPの恩恵を感じられた部分もありました！

引き続きデザインパターンについて知見を貯めていきたいなと考えています！
知見がある方いましたらアドバイスいただきたく思います！

github以下にあります。

https://github.com/kboy-silvergym/KinnikuSwift