2024. 7. 2. 22:33ㆍArchitecture, Design Pattern
💡 글을 시작하기 전에, 아키텍처 패턴 (Architecture Pattern)이 무엇인지 설명하고 넘어가자면!
아키텍처 패턴은 애플리케이션에서 필요한 주요 부분을 각각 분리하여, 역할을 명확하게 구분하는데 사용되는 일종의 "규칙"과 같은 개념이다.
이렇게 역할을 구분함으로써 코드의 유지보수성도 올라가고, 재사용성도 올라가고, 코드 가독성도 높이는 (코드의 한 부분이 너무 길어서 "이게 뭐하는 코드야"라는 생각을 가지지 않아도 되니까!) 효과를 얻을 수 있다.
-> 소프트웨어를 만든다고 하면, 본인도 알게 모르게 기본적인 아키텍처 패턴은 사용되고 있다.
1️⃣ MVC (Model-View-Controller) 패턴이 뭐야?
MVC(Model-View-Controller) 패턴은 가장 기본적인 애플리케이션의 설계 구조를 나타내는 소프트웨어 아키텍처 패턴이다.
Model, View, Controller로 나뉘어져 있는 MVC 패턴의 각 역할을 하나씩 자세하게 살펴보자.
- Model : 데이터, 말 그대로 화면에 "보여질 내용"만을 담당하는 계층이다. (화면 처리에 관한 내용이 아니다!)
- View : 사용자에게 우리가 갖고 있는 데이터가 "보여지는 내용"을 담당하는 계층. (데이터를 저장하는 것이 아니라, 그저 그리는 용도)
- Controller : "Model과 View 사이를 Control"하는 계층이다.
모두가 그런 것은 아니지만, 결국 위 그림에서 의미하는 계층 간 Cycle을 설명해 보자면,
View로 들어온 요청을 Controller로 전달 (user의 action이 들어왔다고 Controller에 알리는 것) -> Controller는 Model에서 바뀐 데이터를 반영 -> Model은 Controller에 데이터의 변경사항을 알림 -> Controller는 바뀐 데이터에 맞게 View 업데이트 순으로 진행이 된다.
2️⃣ Apple의 MVC (Model-View-Controller) 패턴은 조금 다르다면서?
하지만, 실제로 Apple Style의 개발을 하다 보면 위 설명에는 무엇인가 이상한 점이 있다는 것을 눈치챘을지도 모르겠다.
왜냐면 애초에 View와 Controller가 붙어져 있는 ViewController가 기본으로 사용되는 것이 Xcode 프로젝트 파일의 모습이었기 때문이다.
물론 엄밀히 말하면, ViewController 안에서도 View Life Cycle을 가지고 View와 Controller 사이의 인터렉션이 이루어지는 구조이긴 하다.
하지만, 하나의 ViewController라는 파일 안에는 뷰를 그리고, 네트워크 통신으로 데이터를 받아오고, 컬렉션뷰 테이블뷰 같은 경우에는 Delegate나 Datasource 등의 코드도 담겨 있기 때문에 ViewController가 무거워지는 문제가 필연적으로 생길 수밖에 없다.
💡 그래서 Apple MVC Pattern의 근본적인 문제를 Massive ViewController라 부른다.
3️⃣ UITableView 코드에서 확인하는 MVC 구조
간단한 UITableView를 사용한 화면에서 Apple의 MVC Pattern이 어떻게 적용되는 건지 살펴보도록 하겠다.
기본적으로 데이터를 담고 있을 Model, 그리고 테이블 뷰 안에 들어있는 Cell은 View, 그리고 테이블 뷰를 포함하는 큰 ViewController로 나뉘어진다. (테이블 뷰는 사실 별도의 View로 분리해야 하지만, 결국 Datasource와 Delegate를 ViewController에서 선언해야 하기 때문에 분리하지 않고 사용하는 셈이다.)
* ToDoViewController는 View+Controller를 생각하면 이해하기 쉽겠다.
테이블 뷰에 들어가는 데이터는 아래와 같이 Model로 구현했다.
테이블 뷰 안에 있는 Cell은 UILabel과 UISwitch를 담고 있어, 이 각각에 대응될 데이터인 String 타입의 title과 Bool 타입의 isCompleted 변수가 선언되어 있는 것이다.
import Foundation
struct Todo {
var title: String
var isCompleted: Bool
}
Cell 부분의 자세한 코드는 위의 내용으로 생략하고 여기서는 ViewController 코드가 어떻게 구성되어 있는지만 잘 살펴보겠다.
위에서 만들었던 ToDo 이름의 Model이 위에서 설명했듯이 테이블 뷰의 데이터로 사용될 것이다.
tableView와 tableViewCell은 모두 View에 속해 ViewController 안에 담겨 있는 것을 아래에서 코드로 확인할 수 있다.
final class TodoViewController: UIViewController {
// MARK: - Model
var todos: [Todo] = [
Todo(title: "새로나온 아이패드 사기", isCompleted: false),
Todo(title: "영어 공부 해야하는데 언제하려나ㅜㅜㅜ", isCompleted: true),
Todo(title: "하루 하나씩 풀면서 배워가는 코딩 테스트!", isCompleted: false)
]
// MARK: - View (UI Components)
private let todoTableView = UITableView()
func setupRegisterCell() {
todoTableView.register(TodoTableViewCell.self,
forCellReuseIdentifier: "TodoTableViewCell")
}
기본적으로 테이블 뷰는 UITableView의 DataSource 부분에서 Model과 View+Contoller의 인터렉션이 일어나는 셈이다.
= Model에 있는 데이터를 바탕으로 Cell을 그려내는 화살표의 흐름이다.
// MARK: - TableView DataSource
extension TodoViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TodoTableViewCell", for: indexPath) as? TodoTableViewCell else { return UITableViewCell() }
let todo = todos[indexPath.row]
cell.configureCell(forModel: todo)
return cell
}
}
뷰 컨트롤러(ViewController) 코드 전체를 여기서 살펴본 것은 아니지만,
이 부분 외에도 뷰를 등록하는 로직, 테이블 뷰 UI를 설정하는 로직, 오토레이아웃을 잡는 로직, 네트워크 통신 로직 등 뷰 컨트롤러에는 짧은 Model 코드 길이에 비해 많은 비즈니스 코드가 사용되어 Massive VC가 된다는 것을 이를 통해 이해할 수 있을 거라 생각한다.
*정말 간단한 예제임에도 불구하고, 계층 분리가 거의 안이뤄졌다고 볼 수 있는 엄청난 코드 길이의 차이이다.
굉장히 직관적이고, 빠르다는 점에서 MVC Pattern은 명확한 장점을 갖는다. (절대 나쁜 게 아니다!)
하지만, 그에 비해 MVC 패턴이 갖는 단점도 위와 같이 굉장히 명확한 만큼,
이 MVC Pattern을 개선하기 위해 많은 사람들이 다양한 아키텍처 패턴을 고민하게 되었고, 실제로 이를 기본으로 발전된 다양한 아키텍처 패턴들이 등장하게 되었다.
앞으로 블로그 글에서는 이 MVC Pattern을 어떤 고민과 함께 개선된 패턴이 나왔는지 차근차근 살펴보도록 하겠다. 오늘은 여기까지!