11月29日に筋肉.swiftという所謂普通のiOS勉強会を主催しまして、イベントを盛り上げるためにiOSアプリをリリースしました。
今回、作るに当たってある程度テーマを設けたいなと思いまして、
・Cloud Firestoreを使ってみる
・MVPパターンで実装してみる
という2つのテーマに設定しました。
そのうち今回は、後者のMVPパターンで実装してみるをやってみて感じたメリット、疑問に思ったことなどを書いていきたいと思います。
ちなみにソースコードは以下にあります。
MVPパターンとは
出典:https://speakerdeck.com/amacou/mvpfalseyounamofalsewotimuniti-an-sitahua?slide=23 出典:https://speakerdeck.com/amacou/mvpfalseyounamofalsewotimuniti-an-sitahua?slide=29非常にわかりやすかったので上のスライドをシェアさせていただきました。
今回、MVPを採用する目的としては、ViewControllerがでかくなるのを解消できるのか? を個人的に検証するため。サンプルじゃなくて実際に動かすことを目的としたアプリでどうなるのか検証したかったため、筋肉.swiftアプリを使ってやってみました。
以下、実際に実装して見て、メリットを感じた例、意味なくね?と思った例、これでいいのか?と疑問に思った例をあげていきたいと思います。もし知見のある方がいましたらご意見お願いしたいです。
メリットを感じた例
登壇してくれるスピーカー一覧を表示するSpekerListViewControllerを実装するに当たってPresenterはViewControllerを薄くすることに貢献してくれました。
getSpeakerRealtime
、sort
、getVoteAlert
という3つのメソッドがあり、これをこのままViewControllerに書くと60行くらいは占有します。ViewControllerがとてもでかくなって見にくくなるところをPresenterが請け負うことで回避できたと思いました。
意味なくね?と思った例
自分で、**MVPにしてやる!**というテーマに決めたので意味ない気がしても無理やりPresenterを作ることにしました。
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
}
})
}
}
呼び出し側のViewControllerのメソッドが以下。
func getTweet(){
presenter.getTweet({ tweets in
self.tweets = tweets
self.refreshControl?.endRefreshing()
self.tableView.reloadData()
})
}
@IBAction func addButtonTapped(_ sender: Any) {
presenter.showTweetComposer(self)
}
ViewからModelを直接触らないというルールを守るためにTwitterAPIクラスをPresenterにおくようにしてみました。しかし、これではModelを直接触らないルールが守られているだけで、ViewControllerを小さくするという目的が達成されていません。伝言ゲームをしているだけ。
これが意味なくね?と思ったポイントです。
疑問に思った例
さらに、同じ部分のFeedPresenterまわりのMVP実装で疑問に思うことがあります。
以下に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はメンバ変数としてあって欲しいところです。それとも
cell.tweet = presenter.tweet[indexPath.row]
みたいになるべき?
なにかしっくり来ない気がします。
UITableViewDataSource
はView/ViewController側の責務としてあるべきだと思うので、UITableViewDataSource
をPresenterが持つというのもルール違反な気がしています。そもそもそしたら複雑になりそうですし。。。
疑問点以上です。。
追記
例えばこのVCとかで、photoContentsをpresenterに置いたりするべきなのかな?ってことを僕は考えていました。けど結局VCに置いた方がいい感じだったので僕も @fumiyasac さんと同じようにphotoContents的なモデルをVCのメンバ変数に置いて実装しています。https://t.co/8qTNkG1t70
— K-BOY@筋肉.swift (@kboy_silvergym) 2017年11月30日
Twitterでコメントいただいた @fumiyasac@github さんの例でもtableViewの要素を示すphotoContents的なモデルは、ViewControllerのメンバ変数に置いていました。
やはりこれがtableViewにおけるMVPのベストプラクティスかもしれない。
まとめ
ということで、後半はstackOverFlowの質問みたいになってしまいましたが、**ViewControllerを薄くしたい!**という目的においてMVPの恩恵を感じられた部分もありました!
引き続きデザインパターンについて知見を貯めていきたいなと考えています!
知見がある方いましたらアドバイスいただきたく思います!
github以下にあります。