[Swift] tableView를 사용한 MVC에 대한 고찰

팔랑이·2024년 7월 16일
0

iOS/Swift

목록 보기
48/71
post-thumbnail

tableView 사용을 예시로 들어 MVC에 대한 고찰을 해보고자 한다.
어떤 뷰 안에 tableView를 넣는다고 했을 때, 지금까지 내가 일반적으로 본 방법은 3가지인데, 이것들의 차이점에 대해 작성해보고자 한다.

1. ViewController.swift 안에서 테이블뷰 생성

기능 실습용 강의를 들을 때 가장 많이 나오는 방법인 것 같다.
뷰컨트롤러 안에서 TableView를 정의하고 extension으로 추가 프로토콜들을 받아 설정한다.

ViewController.swift:

import UIKit

class ViewController: UIViewController {

    private let tableView: UITableView = {
        let tableView = UITableView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self

        // SnapKit 사용하여 제약 설정
        tableView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
    
extension ViewController: UITableViewDelegate {

}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = "Row \(indexPath.row)"
        return cell
    }
}

2. UIView 상속받는 클래스 생성

테이블뷰를 별도의 커스텀 뷰로 만들고 뷰 컨트롤러에서 이 커스텀 뷰를 사용하는 방식이다. 스토리보드 기반으로 iOS 개발을 처음 접했던 나로서는 이 방법이 다소 신기했음.

ContactTableView.swift:

import UIKit
import SnapKit

class ContactTableView: UIView {

    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        return tableView
    }()

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

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupTableView() {
        addSubview(tableView)
        tableView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }

    func setDataSourceDelegate(dataSource: UITableViewDataSource, delegate: UITableViewDelegate) {
        tableView.dataSource = dataSource
        tableView.delegate = delegate
    }
}

ViewController.swift:

import UIKit

class ViewController: UIViewController {

    private let contactTableView = ContactTableView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(contactTableView)
        contactTableView.setDataSourceDelegate(dataSource: self, delegate: self)

        // SnapKit 사용하여 제약 설정
        contactTableView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }

extension ViewController: UITableViewDelegate {

}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = "Row \(indexPath.row)"
        return cell
    }
}

3. UITableViewController 상속받는 클래스 생성

이 방법은 테이블뷰 컨트롤러를 별도의 클래스로 만들어 사용하는 방법이다. UITableViewController를 상속받아 구현하므로, 테이블뷰 컨트롤러가 테이블뷰와 관련된 기본 설정을 자동으로 처리한다.

ContactTableViewController.swift:

import UIKit

class ContactTableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = "Row \(indexPath.row)"
        return cell
    }
}

ViewController.swift:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let contactTableViewController = ContactTableViewController()
        addChild(contactTableViewController)
        view.addSubview(contactTableViewController.view)
        contactTableViewController.didMove(toParent: self)

        // SnapKit 사용하여 제약 설정
        contactTableViewController.view.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
}

1번, 2번 코드와 가장 다른점은 addChild를 통해 ViewController에서 TableViewController를 등록해줘야 한다는 것이다.

중간 요약
1. ViewController 내에 직접 테이블뷰 생성: 간단하고 빠르게 테이블뷰를 설정할 수 있다.
2. UIView를 상속받아 테이블뷰 생성: 뷰 재사용이 용이하고, 코드 구조가 분리되어 유지보수가 쉽다.
3. UITableViewController를 상속받아 테이블뷰 생성: 테이블뷰와 관련된 설정이 자동으로 처리되며, 테이블뷰 전용 컨트롤러를 만들 때 유용하다.

그렇다면 이 중 MVC에 가장 가까운 방식은?

MVC (Model-View-Controller) 패턴에서 중요한 점은 각 컴포넌트가 명확히 분리되어 있어야 한다는 것이다. 즉, 모델은 데이터와 비즈니스 로직을 담당하고, 뷰는 사용자 인터페이스 요소를 담당하며, 컨트롤러는 뷰와 모델 간의 상호작용을 관리한다.

노스토리보드 세팅된 UIKit에서, 위 세 가지 방법 중 MVC 패턴에 가장 잘 맞는 방법은 UIView 상속받는 클래스를 생성하는 두 번째 방법이다.

  • View: ContactTableView 클래스는 테이블뷰와 관련된 모든 UI 요소를 담당하며, 뷰 컨트롤러와 독립적으로 작동한다.
  • Controller: ViewController 클래스는 데이터 소스와 델리게이트 메서드를 구현하여 뷰와 모델 간의 상호작용을 관리하며,뷰와 모델 사이의 중개자 역할을 한다.

2번 방법과 3번 방법의 비교

3번 방법이 delegate와 datasource를 같이 관리해주니 코드 짤 때는 더 편해 보이는데, mvc적으로나 코드 관리 효율적으로 2번 방법이 더 좋은 것일까? 이에 대해서는 지피티한테 한번 물어봤다...

이에 대한 지피티의 대답 요약:

UITableViewController를 사용하는 방법은 특정 상황에서 유용할 수 있으며, 다음과 같은 이유로 주로 사용된다:

  1. 화면 전체가 테이블뷰로 구성된 경우: UITableViewController는 테이블뷰의 설정, 델리게이트 및 데이터 소스를 자동으로 관리해주기 때문에 간편하고, 화면 전체가 TableView로 이루어져 있을 때 매우 효율적이다.

  2. 간편한 설정: UITableViewController는 기본적인 테이블뷰 설정을 자동으로 처리합니다. 예를 들어, 스크롤 동작, 인디케이터 표시, 키보드 처리 등 테이블뷰와 관련된 여러 기능을 기본 제공하므로, 코드 작성이 간결해집니다.

그러나, UIViewControllerUITableView를 사용하는 두 번째 방법이 MVC 패턴을 더 잘 따르는 이유는 다음과 같다:

  1. 유연성: UIViewController를 사용하면 테이블뷰 외에 다른 UI 컴포넌트도 함께 관리할 수 있다. 이는 복잡한 화면을 구성시 더 유연하게 작업할 수 있다.

  2. 명확한 역할 분리: UIViewController를 사용하면 테이블뷰 외에 다른 뷰와 로직을 명확하게 분리할 수 있다. 이는 유지보수와 확장성 측면에서 유리하다.

  3. 커스터마이징: UIViewController를 사용하면 테이블뷰의 배치와 레이아웃을 더 세밀하게 제어할 수 있다.

결론적으로, UITableViewController는 테이블뷰가 화면의 주요 요소일 때 간편하고 효율적으로 사용할 수 있지만, UIViewControllerUITableView를 함께 사용하는 방법이 더 유연하고 MVC 패턴을 더 잘 따른다. 따라서, 복잡한 UI를 구성하거나 다양한 컴포넌트를 함께 사용할 때는 두 번째 방법이 선호된다.


이 글을 만약 누군가 보시게 된다면...
더 일반적인 방법이 있다던가 첨언/조언,
또는 반박이 있으시면 댓글에 자유롭게 남겨주시면 좋겠다...

profile
정체되지 않는 성장

0개의 댓글