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.
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.
Controller
that prepares an intermediary so that objects do not refer to each other and weakens the degree of coupling between objects is called the ** Mediator pattern **.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
.
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
.
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.
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
.
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!
Model-View-Controller PEAKS | Introduction to iOS App Design Patterns