티스토리 뷰

스위프트

[Swift] MVC 패턴

강철곰탱이 2023. 10. 31. 23:57

swift uikit의 대표적인 패턴인 MVC 패턴에 대해 알아보자.

 

✏️ MVC 패턴이란?

 

: MVC패턴은 Model + View + Controller 구조의 아키텍처 패턴을 말한다.

 

1️⃣ Model

:Model은 UI와 직접적으로 연결되지 않는다. data를 어떻게 관리할지가 중요하지, 이 data가 UI에서 어떻게 보일지는 신경쓰지 않아도 된다는 것이다.

  • 데이터와 관련된 내용
  • 데이터를 관리하는 로직

 

2️⃣ View

: View를 작성할 때는 재사용성이 강조된다. 화면에 들어가는 여러 요소들 중 자주 사용하는 요소는 재활용할 수 있도록 따로 만들어 관리하면 코드가 깔끔해질 것 이다.

 

  • 사용자에게 보여주는 UI를 담당
  • 상호작용을 Controller 계층으로 전달하는 역할

 

 

3️⃣ Controller

 

: 앱의 핵심 로직을 가지며, Model과 View의 중간자 역할을 한다. MVVM 패턴에서 ViewModel이 하는 역할과 비슷하다고 할 수 있다.

 

  • View에 보여주기 위한 데이터를 Controller가 Model에서 가져오는 기능
  • View로 부터 유저와의 상호작용에 대한 정보를 받고, 해당 정보로 Model 정보 업데이트

 

 

📍일반적인 MVC 패턴

 

 

일반적인 흐름은,

 

  1. View에서 사용자의 입력을 받아 Controller에게 전달
  2. Controller는 입력에 따라 Model 변경
  3. 변경된 Model을 토대로 View 변경

 

=> Model과 Controller, View 사이가 밀접하게 연결되어 있어, 독립성이 없으므로 테스트하기 어렵고 재사용성이 떨어진다.

 

그렇다고 이 패턴이 안좋은가? 는 아니다.

 

규모가 큰 프로젝트에는 Model, View, Controller 외에 추가해야 할 것이 많아 적합하지 않지만, 규모가 작은 프로젝트에서는 사용하기 적합하다고 볼 수 있다.

 

 

📍Apple CocoaMVC 패턴

 

 

https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52

 

 

View와 Model 사이의 연관성이 사라진 것을 확인할 수 있다.

View가 Model의 data를 직접 가져오는 것이 아닌 중간자 Controller를 통해 관리한다.

 

 

https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52

 

 

View와 Controller가 강한 의존성을 가져 View와 Controller를 따로 테스트하는 것은 어렵다.

또한 View와 Model을 Controller가 혼자 관리하기 때문에 코드가 Controller에 집중되어 코드가 복잡해질 수 있다.

 

 

 

✏️ iOS에서 MVC 패턴이란?

 

Controller는 Model과 View에 직접적으로 접근할 수 있지만 Model과 View는 Controller에 직접적으로 접근할 수 없다.

Model과 View는 어떻게 Controller에게 변경사항을 알릴까?

 

 

📍Model -> Controller

 

: Model이 Controller에게 전달해야 하는 경우는 데이터가 변화했다는 사실 정도이다.

Model은 데이터를 제공하는 코드이지, Controller가 어떻게 만들어졌는지 모르는 구조이니 직접적인 접근은 불가능하다.

 

Model의 데이터 변화 감지 방법은 두 가지이다.

 

  • KVO
  • Notification

 

1️⃣ KVO

 

: Controller는 Model에 있는 KVO(Key-Value-Observing)을 이용해 프로퍼티의 변화를 감지하여 데이터를 받아올 수 있다.

 

 

⁇ KVO란

더보기

KVO는 Key-Value Observing의 약자로, Objective-C 및 Swift에서 프로퍼티의 변화를 감지하기 위한 메커니즘이다. 이는 객체 간에 특정 프로퍼티의 변화를 관찰하고 대응하는 디자인 패턴 중 하나이다.

 

  • 특정 객체의 프로퍼티가 변경될 때마다 관찰자(Observer)에게 알리는 것이 가능
  • 주로 모델의 데이터 변경을 뷰나 컨트롤러에 통지하고, 그에 따른 업데이트를 수행할 때 활용된다.

 

KVO는 주로 모델의 변경을 감지하고, 해당 변경에 대한 업데이트를 뷰나 컨트롤러에 전달하는 데에 사용된다. 그러나 KVO는 Objective-C 기반의 메커니즘이기 때문에, Swift의 타입 안정성을 완전히 지원하지는 않는다. 따라서 사용 시 주의가 필요하며, 가능하면 Swift의 propertyObservers나 Combine과 같은 더 현대적인 방식을 사용하는 것이 권장된다.

 

 

2️⃣ Notification

 

: 변화했다는 신호를 보내놓으면, Controller가 그 알림을 보고 Model에 접근해 데이터를 받아올 수 있다.

 

 

 

📍View -> Controller

 

: View가 Controller에게 전달하는 경우는 유저와의 상호작용에 대해 전달하는 경우이다.

 

View에서 유저의 action이 발생하는 경우는 다음과 같다.

 

  • delegate
  • datasource

 

 

=> Model과 View 모두 Controller에 접근할 수 없으므로 결국 Controller가 Model과 View가 해야할 일을 "위임"받아 하고 있는 느낌이다.

 


✏️ MVC 패턴 예시

 

TableView에 Cell을 지정하고 delegate와 datasource를 사용하는 예제이다.

 

 

Model

 

// 모델 구조체
struct Item {
    var name: String
}

 

 

View

 

// 테이블뷰 셀 클래스
class ItemTableViewCell: UITableViewCell {
    // 모델 데이터를 표시할 라벨
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        return label
    }()
    
    // 모델 업데이트 메서드
    func update(with item: Item) {
        nameLabel.text = item.name
    }
    
    // 초기화 메서드
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        // 라벨을 셀의 컨텐츠뷰에 추가
        contentView.addSubview(nameLabel)
    }
    
    // 레이아웃 설정 메서드
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // 라벨 프레임 설정
        nameLabel.frame = contentView.bounds
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

 

 

Controller

 

// 뷰컨트롤러 클래스
class ItemViewController: UIViewController {
    // 모델 데이터 배열
    var items: [Item] = []

    // 테이블뷰
    private let tableView: UITableView = {
        let tableView = UITableView()
        tableView.register(ItemTableViewCell.self, forCellReuseIdentifier: "ItemCell")
        return tableView
    }()

    // 뷰가 로드된 후 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 모델 데이터 생성
        items = [
            Item(name: "아이템 1"),
            Item(name: "아이템 2"),
            Item(name: "아이템 3")
        ]
        
        // 테이블뷰를 뷰에 추가
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self
    }

    // 레이아웃 설정 메서드
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // 테이블뷰의 프레임 설정
        tableView.frame = view.bounds
    }
}

// MARK: - UITableViewDataSource
extension ItemViewController: UITableViewDataSource {
    // 테이블뷰 데이터 소스 메서드 - 섹션 내 로우 개수 반환
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    // 테이블뷰 데이터 소스 메서드 - 각 셀 구성
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemTableViewCell
        
        // 해당 인덱스의 아이템을 셀에 업데이트
        cell.update(with: items[indexPath.row])

        return cell
    }
}

// MARK: - UITableViewDelegate
extension ItemViewController: UITableViewDelegate {
    // 테이블뷰 델리게이트 메서드 - 셀이 선택되었을 때
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 선택된 셀의 처리
        print("선택된 아이템: \(items[indexPath.row].name)")
    }
}

 

 

TableView의 cell인 ItemTableViewCell와 ViewController의 TableView도 View를 담당한다.

나머지 부분은 ViewController를 담당하며, View의 Life Cycle도 포함된다.

 

delegate와 datasource를 통해 View를 어떻게 보여줄지 구현했다.

 

 

✏️ MVC 패턴 장점과 단점

 

🟢 장점

 

  • Model, View, Controlle로 역할 분리함으로써 코드의 가독성과 유지보수성 향상
  • 개발속도가 빨라 규모가 작은 프로젝트에 사용하기 적합
  • 각 컴포넌트가 독립적으로 동작하여 재사용에 용이

 

 

🔴 단점

 

  • View와 Controller간의 강한 의존성이 발생할 수 있므여, 이는 테스트 및 재사용성을 어렵게 만들 수 있다.
  • 큰 규모의 프로젝트에서는 각 컴포넌트가 복잡해져, 이로인해 코드의 이해와 유지보수가 어려워질 수 있다.
  • View와 Controller 사이에 중복 코드가 발생할 수 있다.
  • 대부분의 코드(delgate, datasource, 네트워크 요청, DB에 데이터 요청 등)가 Controller에 밀집하여 Controller 크기가 커지고 내부 구조는 복잡하게 된다. => 이를 Massive View Controller라고 한다.

 


 

참고

 

'스위프트' 카테고리의 다른 글

[Swift] - CGAffineTransform 사용  (0) 2023.11.27
[Swift] - Alamofire 사용법  (0) 2023.11.20
[Swift] CocoaPod 설치와 사용법  (0) 2023.10.30
[Swift] self는 무엇인가?  (0) 2023.10.29
[Swift] .self는 무엇인가?  (0) 2023.10.29
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함