[SWIFT] Why Cocoa MVC tends to be Fat

Introduction

FatViewController is an inseparable problem when developing iOS apps. Even so, do you all have the impression that Cocoa MVC tends to be FatViewController? MVC is an abbreviation for Model View`` Controller, but it is sometimes called MassiveViewController because ViewController tends to grow. So why is Cocoa MVC so easy to get fat? I investigated it this time.

What is Cocoa MVC in the first place?

Cocoa MVC is an architecture that divides the roles into three parts: Model View`` Controller.

name role
Model It retains and processes data and notifies you of status updates.
View Performs screen drawing processing.
Controller It accepts processing from the user, requests processing to Model based on it, detects that Model has been changed, and performs drawing update processing of View.

Controller has two references, View and Model. Controller requests the update process from Model based on the user action (button tap etc.) received from View, and Model updates the data based on the request. Controller detects that the data of Model has been updated, and updates the drawing of View based on the detected contents. The Controller acts as a bridge between the View and the Model, so the View and the Model do not refer to each other.

スクリーンショット 2020-12-21 20.31.55.png

Why ViewController becomes Fat

I explained the outline of Cocoa MVC, but on top of that, why is Cocoa MVC so easy that ViewController becomes Fat so that it is called MassiveViewController? Next, let's look at the parts that are likely to become FatViewController.

Recognition that the UI should also be controlled by the UIViewController

As I did, many people tend to put the responsibilities of both View and Controller on the class of UIViewController without dividing the responsibilities. is. So why do we implement the View and Controller in one class without separating them? The origin is in UIViewController in the first place.

UIViewController is UIKit, that is, it is provided as a component for controlling the UI. So, UIViewController has a screen life cycle and draws View by connecting with Storyboard. From here, we tend to put the responsibility as a View on the UIViewController as well as the responsibility as a Controller. The result is our FatViewController.

スクリーンショット 2020-12-21 20.55.46.png

The role of view controllers in Cocoa MVC

I understand why Cocoa MVC tends to be FatViewController. So how do you not become Fat? The answer is quite simple. The role of UIViewController in Cocoa MVC is to bridge and control View and Model as Controller, and by devoting UI control to UIView, UIViewController becomesView It makes it not to have the role of. Don't lose the temptation to control the UI with UIViewController and divide your responsibilities exactly. That is the first step to get out of Fat.

Try implementing Cocoa MVC

Now, let's look at a description example of Cocoa MVC when the ViewController is actually given only the responsibility of Controller through the code.

ArticlesViewController.swift


import UIKit

///Controller for displaying the article list
final class ArticlesViewController: UIViewController {

    // MARK: - Variables

    ///View to display
    private lazy var articleView = ArticlesView()

    ///Model used for display
    private var model: ArticlesModel? {
        didSet {
            registerModel()
        }
    }

    ///Article data
    private var articles: [Article] = [] {
        didSet {
            articleView.articlesTableView.reloadData()
        }
    }

    // MARK: - Lifecycle Methods

    override func loadView() {
        super.loadView()
        view = articleView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        model = ArticlesModel()
        model?.request()
    }
}

// MARK: - Private Methods
private extension ArticlesViewController {
    ///Processing at the time of model registration(Start combining Model and View and monitoring Model)
    func registerModel() {
        model?.delegate = self
        articleView.articlesTableView.dataSource = self
        articleView.articlesTableView.delegate = self
    }
}

// MARK: - UITableViewDataSource
extension ArticlesViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return articles.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //Process for building Cell for article list(Since the story deviates from the content of this article, the details of the process are omitted.)
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "ArticleCell", for: indexPath) as? ArticleCell else {
            return UITableViewCell()
        }
        let article = articles[indexPath.row]
        cell.configure(with: article)
        return cell
    }
}

// MARK: - UITableViewDelegate
extension ArticlesViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //Describe the processing at the time of tapping
    }
}

// MARK: - ArticlesModelDelegate
extension ArticlesViewController: ArticlesModelDelegate {
    func completedRequest(result: Result<[Article], Error>) {
        switch result {
        case .success(let value):
            articles += value
        case .failure(let error):
            print(error)
        }
    }
}

ArticlesView.swift


import UIKit

///Article list View
final class ArticlesView: UIView {

    // MARK: - Constants

    /// identifer
    private static let identifer = "ArticlesView"

    // MARK: - Outlets

    ///TableView for displaying the article list
    @IBOutlet private(set) weak var articlesTableView: UITableView! {
        didSet {
            let identifer = type(of: self).identifer
            articlesTableView.register(UINib(nibName: identifer, bundle: nil), forCellReuseIdentifier: identifer)
        }
    }

    // MARK: - Lifecycle Methods

    override init(frame: CGRect) {
        super.init(frame: frame)
        initialze()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        initialze()
    }
}

// MARK: - Private Methods
private extension ArticlesView {
    ///initialize
    func initialze() {
        guard let view = Bundle.main.loadNibNamed(type(of: self).identifer,
                                                  owner: self,
                                                  options: nil)?.first as? UIView else {
            return
        }
        view.frame = self.bounds
        self.addSubview(view)
    }
}

ArticlesModel.swift


import Foundation

/// ArticlesModelDelegate
protocol ArticlesModelDelegate: class {
    ///Notify when article request is completed
    /// - Parameter result:Request result
    func completedRequest(result: Result<[Article], Error>)
}

///Article list Model
final class ArticlesModel: NSObject {

    // MARK: - Variables

    /// ArticlesModelDelegate
    weak var delegate: ArticlesModelDelegate?

    ///article
    private(set) var articles: [Article] = []

    ///Request an article
    func request() {
        //Processing to request a list of articles(Since the story deviates from the content of this article, the details of the process are omitted.)
        ApiClient.request(completion: { [weak self] result in
            self?.delegate?.completedRequest(result: result)
        })
    }
}

I tried to describe it roughly, but how about it? At least, the ViewController only has the role of Controller, and the processing related to screen drawing has disappeared, so the outlook has improved a little. By properly dividing the responsibilities as described above, you may be able to avoid the fat ViewController.

in conclusion

This time, I have summarized my thoughts on why Cocoa MVC tends to be FatViewController. Of course, there are various countermeasures to prevent it from becoming FatViewController. If you're bloating with Cocoa MVC, you can of course use a different architecture. Let's eradicate FatViewContoller by selecting the appropriate architecture as needed and carefully considering and implementing the responsibilities that each class should have!

Also, this article is my own judgment and prejudice, so if you have any opinions or suggestions for improvement, I would be very grateful if you could comment. Thank you for reading!

References

Model-View-Controller PEAKS | Introduction to iOS App Design Patterns

Recommended Posts

Why Cocoa MVC tends to be Fat
Why null is said to be bad
Why Fat Controller Aunt