MVC의 초기 버전은 View를 통해 사용자와 상호작용하고, 이 동작을 컨트롤러에게 보내면 Model이 데이터를 업데이트하고 View를 다시 그리는 방식으로 설계되었다. Model이 업데이트 되면 Model이 View에게 알림을 보내고 View의 상태를 다시 받아온다.
현재 사용되는 MVC 패턴은 현대의 소프트웨어와 디바이스에 맞춰서 업데이트된 버전이다.
모바일 애플리케이션에서는 view와 같은 컴포넌트를 재사용하는 일이 흔하기 때문에 Model과 View가 서로 연결되지 않도록 기존 MVC 패턴을 개선하였다.
이제 View는 사용자의 액션을 받고 Controller에게 이를 알려준다. 그리고 컨트롤러는 Model을 업데이트하고 그 다음 View를 업데이트한다.
Apple은 MVC가 다음 세 가지 디자인 패턴을 기본으로 한다고 한다.
비즈니스 로직을 담당하고 애플리케이션 내 데이터에 접근, 조작, 저장한다.
Model에 포함되는 클래스는 다음과 같다.
Model은 View와 직접 커뮤니케이션 할 수 없다. Model과 View 사이 통신은 반드시 Controller를 통해 이루어져야 한다.
View는 사용자가 보는 컴포넌트들로 이루어져 있다.
Model과 View 사이의 중개자 역할을 한다.
MVC 패턴의 주요한 단점들은 Controller에 있는데, 이 이유는 다음과 같다.
이런 문제들을 해결하기 위해 view와 관련된 코드를 다른 클래스로 옮기고, view controller 간의 내비게이션을 coordinator로 분리하거나 UITableViewController나 UICollectionView의 Delegate와 data source를 view controller 외부로 전달할 수 있다.
이 부분은 실제 코딩이랑 관련된 내용들이라… 흥미롭거나 기억하고 싶은 부분들만 적어둬서 글이 매끄럽지 않을 수 있습니다 😅
Model-View-Controller 그대로 폴더를 생성한다.
UI 관련, 사용자와의 상호작용이 일어나는 인터페이스
UIViewController의 하위 클래스로, 앱의 주요 부분이며 model과 view를 연결한다.
MVC 패턴에서 어떻게 정보를 전달할 수 있을까?
컨트롤러는 뷰와 모델에 대한 레퍼런스(인스턴스)를 포함한다. 따라서 컨트롤러는 뷰와 모델의 public 메소드를 사용해서 정보를 바로 전달할 수 있다.
let tasksLists = tasksListService.fetchLists()
let view = HomeView()
view.setLists(taskLists)
그럼 사용자가 View와 상호작용한 후에는 이를 컨트롤러에 어떻게 전달할 수 있을까?
View는 메소드를 호출할 컨트롤러 인스턴스를 가지고 있지 않기 때문에 Delegate 패턴을 사용해야 한다.
Delegate 패턴은 클래스가 다른 클래스의 인스턴스의 책임을 대신 가질 수 있도록 한다. 사용자의 상호작용에 대한 동작은 Controller에서 구현된다.
iOS 13 이후 AppDelegate의 일부 역할이 SceneDelegate로 옮겨졌다. AppDelegate는 앱의 생명주기와 관련 설정을 담당하고, SceneDelegate는 화면에서 무엇이 보여지는지와 어떻게 보여지는지를 담당한다.
SceneDelegate에 앱의 초기 화면을 띄우는 코드를 작성한다.
View Controller의 이니셜라이저에 필요한 클래스의 인스턴스를 전달한다. 이를 의존성 주입(Dependency Injection)이라고 하는데, 컴포넌트 간 결합도를 낮춰서 유닛 테스트를 더욱 쉽게 만들어준다. (mock object를 만드는 등)
class HomeViewController: UIViewController {
private var homeView = HomeView()
private var tasksListService: TasksListServiceProtocol!
private var taskService: TaskServiceProtocol!
init(tasksListService: TasksListServiceProtocol,
taskService: TaskServiceProtocol) {
super.init(nibName: nil, bundle: nil)
self.tasksListService = tasksListService
self.taskService = taskService
}
...
}
func setTasksLists(_ lists: [TasksListModel]) {
tasksList = lists
tableView.reloadData()
emptyState.isHidden = tasksList.count > 0
}
좋은 아키텍처의 특징 중 하나는 테스트가 가능하다는 것이다.
테스트 코드를 작성할 땐 FIRST라는 기준을 고려해야 한다.
@testable import MVC_MyToDos
var sut: HomeView!
system under test
의 약자이다.testViewLoaded_whenViewIsInstantiated_shouldBeComponents
test
로 시작해야 한다.테스트 할 함수 이름_어떤 상황인지_무슨 결과를 얻어야 하는지
가 드러나도록 지으면 좋다.private으로 선언한 컴포넌트들은 클래스 외부에서 접근해서 읽을 수는 있지만 수정할 수는 없다. → 테스트 가능
두 클래스들은 기존 클래스의 메소드를 오버라이딩하지만 데이터베이스에 접근하지 않는다.
컨트롤러의 테스트는 Model과의 상호작용과 다른 화면으로의 내비게이션에 중점을 둔다.
MockTasksListSerivce와 MockTaskService 클래스를 통해 의존성 주입이 테스트를 얼마나 편하게 만드는지 알 수 있다.
서로 다른 컴포넌트들이 알맞은 정보를 보여주고 있는지, 사용자의 상호작용이 예상한 결과를 만들어내는지를 테스트한다.
+) 적어도 MVVM까지는 쭉 읽어볼까 했었는데.. 아직 MVC 패턴도 완전히 체화되지 않은 상태라서 졸업프로젝트에서 책에 나온 MVC 패턴 적용 방법을 그대로 따라해본 다음 MVC 패턴에 익숙해지면 그 다음 아키텍처 패턴도 공부해 볼 예정이다.