티스토리 뷰
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 패턴
일반적인 흐름은,
- View에서 사용자의 입력을 받아 Controller에게 전달
- Controller는 입력에 따라 Model 변경
- 변경된 Model을 토대로 View 변경
=> Model과 Controller, View 사이가 밀접하게 연결되어 있어, 독립성이 없으므로 테스트하기 어렵고 재사용성이 떨어진다.
그렇다고 이 패턴이 안좋은가? 는 아니다.
규모가 큰 프로젝트에는 Model, View, Controller 외에 추가해야 할 것이 많아 적합하지 않지만, 규모가 작은 프로젝트에서는 사용하기 적합하다고 볼 수 있다.
📍Apple CocoaMVC 패턴
View와 Model 사이의 연관성이 사라진 것을 확인할 수 있다.
View가 Model의 data를 직접 가져오는 것이 아닌 중간자 Controller를 통해 관리한다.
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 |