LoginSignup
469
458

More than 5 years have passed since last update.

これが最強のMVC(iOS)

Last updated at Posted at 2015-08-29

前に、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

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

469
458
24

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
469
458