Edited at

これが最強のMVC(iOS)

More than 3 years have passed since last update.

前に、MVCについての記事を書かせて頂いたのですが(おデブになりがちな、UIViewControllerをスッキリさせる魔法のルール)、色々調べ考えている内に「間違ってるぞ」ということに気づきました。

それを踏まえ、実際にUITableViewを使った実際に良くありそうなコードを具体例に上げご説明したいと思います。


そもそもMVCとは

MVCとは、有名なこのスライドに書かれている通りこれ「やはりお前らのMVCは間違っている」です。

やはりお前らのMVCは間違っている.png

しかしほとんどの世に出ているiosアプリのMVCの構造を見ると以下の図の様になっています。

(これはこのスライドの中で全力でdisられているMVCです)

やはりお前らのMVCは間違っている.png

そして私は、「なんだよ、みんなMVC間違ってんじゃん、ははーん」と思い前回の記事を書きました。

おデブになりがちな、UIViewControllerをスッキリさせる魔法のルール

この記事鵜呑みにした人ごめんなさい、完全に間違ってました。

なぜ間違ってたかと申し上げますと。

公式のマニュアルにこんな記述がございました。

要約すると、

「従来のMVCでも全く問題無いが理論上の問題があります」 ← どゆことやねん

https://developer.apple.com/jp/documentation/CocoaEncyclopedia.pdf

https___developer_apple_com_jp_documentation_CocoaEncyclopedia_pdf.png

さらにCocoaで使われているMVCがコレだとのこと

「完全にあのスライドでdisられてるパターンやーー」

https___developer_apple_com_jp_documentation_CocoaEncyclopedia_pdf.png

と紆余曲折があり、やはり従来のMVCは使うべきではないと結論づけました。

では改めまして


これが最強のMVCやーーー

いくぞおーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー


まずはView編


1.必ずカスタムViewを作る

通常UIViewControllerのviewプロパティにUIViewのインスタンスが割り当てられます。それをカスタムUIViewに変えましょう。

たまにやり方を間違って以下の様に書く人がいるのですが、これでは間違いです。

まあ、動くには動きますが。


Controller.swift

override func viewDidLoad() {

super.viewDidLoad()
self.view.addSubview(CustomView())
}

カスタムViewを設定する場合は以下の様に書きましょう。


Controller.swift

override func loadView() {

self.view = CustomView()
}



2.画面に表示させたい物は全てカスタムViewのイニシャライザでaddSubViewする(Viewのサイズ、位置調整はここではしない)


カスタムView.swift

 required init(model: QiitaViewModel) {

table = UITableView(frame: CGRectMake(0, 0, 0, 0), style: UITableViewStyle.Plain);
refreshControl = UIRefreshControl()
table.addSubview(refreshControl)

super.init(frame: CGRectMake(0, 0, 0, 0));
self.addSubview(table);
}



3.Viewのサイズ、位置調整はlayoutSubviewsで行う。

これは、画面回転をさせた時に恩恵を感じます。


カスタムView.swift

override func layoutSubviews() {

super.layoutSubviews()
table.frame = self.frame
}



4.カスタムViewの中にコントローラーから制御したいインスタンスがあるなら公開しちゃいましょう。

例えばTableViewのbackgroundColorの色を変えたい時に、カスタムViewに「tableColor」的なメソッドを作って、そこを介してTableの色を変えるということをやってる記事をちょくちょく見かけるのですが、それはちょっと違うと思います。

そうする理由は分かります、要するにカプセル化にこだわっているわけです。

でもなんの為のカプセル化を考えればもうちょっと違う判断ができるのではないかと思います。

これに関しては多くは書きません、話が飛躍するので。

ということで、Viewの中にコントローラーから制御したいインスタンスがあるなら公開しちゃいましょう。

それとMVC的に見た時に、ボタンタップ等のイベントはコントローラ側で受け取る必要があるので、公開しておかないと相当めんどいことになる


次にModel編


1.すべこべ言わずに一つのコントローラーに一つのModelを作りましょ。

こいつにロジックを突っ込むわけですね。


Controller.swift

class QitaListViewController: UIViewController,UITableViewDelegate {

private let mModel = QiitaViewModel();


最後にController編


1.カスタムViewの中のViewを直接触る

上で説明した内容と少しかぶりますが、カスタムViewのsubViewのあたるViewをコントローラから直接触って制御しています。


Controller.swift

override func viewDidLoad() {

super.viewDidLoad()
let qiitaListView = self.view as! QiitaListView
qiitaListView.refreshControl.addTarget(self, action: "tableUpdate:", forControlEvents: UIControlEvents.ValueChanged)
qiitaListView.table.delegate = self;
qiitaListView.table.dataSource = mModel;
}


2.delegateはself,dataSourceはmodel

上のコードでは、tableのdategateにselfを突っ込んで、dataSourceには作ったmodelを突っ込んでいます。

これはどうしてかと申しますと、dataSourceに関しては、すべてdataSourceメソッド内だけで完結させる処理を書きますが、delegateに関しては、例えばセルがタップされたら別の画面へ遷移といったようなことをする必要があり、modelに書くのは得策ではないので、delegateはselfを突っ込みます。


細かいこところはソースみてー

全部説明するのは辛いので、ビルド実行できるものをアップしているので、細かい所はそこでご確認ください。

https://github.com/yamasakitomohiro/PerfectMVC

質問も、ご批判も、どんとこいです。