2. MVC: Model-View-Controller

Seoyoung Lee·2023년 3월 19일
0

MVC란?

MVC의 역사

MVC의 초기 버전은 View를 통해 사용자와 상호작용하고, 이 동작을 컨트롤러에게 보내면 Model이 데이터를 업데이트하고 View를 다시 그리는 방식으로 설계되었다. Model이 업데이트 되면 Model이 View에게 알림을 보내고 View의 상태를 다시 받아온다.

Apple의 Model-View-Controller

현재 사용되는 MVC 패턴은 현대의 소프트웨어와 디바이스에 맞춰서 업데이트된 버전이다.

모바일 애플리케이션에서는 view와 같은 컴포넌트를 재사용하는 일이 흔하기 때문에 Model과 View가 서로 연결되지 않도록 기존 MVC 패턴을 개선하였다.

이제 View는 사용자의 액션을 받고 Controller에게 이를 알려준다. 그리고 컨트롤러는 Model을 업데이트하고 그 다음 View를 업데이트한다.

Apple은 MVC가 다음 세 가지 디자인 패턴을 기본으로 한다고 한다.

  • Compostie
    • View는 다른 view들로 이루어져 있다. (예: 버튼, 이미지 등으로 구성된 view)
  • Strategy
    • 컨트롤러는 하나 이상의 뷰(내부 로직이 없어서 재사용이 가능)를 관리한다.
  • Observer
    • 컨트롤러는 애플리케이션의 상태(예: Model에 있는 데이터)가 변경되는 시점을 알아야 하는데, 이런 변경들을 구독(?)하고 있어야 한다.

MVC의 구성요소

Model

비즈니스 로직을 담당하고 애플리케이션 내 데이터에 접근, 조작, 저장한다.

Model에 포함되는 클래스는 다음과 같다.

  • 데이터 유지와 관련된 클래스
    • 예: 데이터베이스의 사용(Core Data, SQLite, Realm), 사용자 설정(UserDefaults)
  • 애플리케이션의 통신(네트워킹)을 관리하는 클래스 → 데이터를 주고받을 수 있도록 한다.
  • 앱 외부에서 받은 데이터를 파싱하고 모델 객체로 변환하는 클래스
  • 익스텐션, 상수, 헬퍼 클래스들

Model은 View와 직접 커뮤니케이션 할 수 없다. Model과 View 사이 통신은 반드시 Controller를 통해 이루어져야 한다.

View

View는 사용자가 보는 컴포넌트들로 이루어져 있다.

  • UIKit, AppKit, CoreAnimation, Core Graphics 라이브러리를 사용한 클래스들
  • Model로부터 받은 데이터를 보여준다. 이때 이 데이터는 Controller를 통해 간접적으로 받아온다.
  • 사용자와의 상호작용이 가능하다.

Controller

Model과 View 사이의 중개자 역할을 한다.

  • MVC 패턴의 가장 중심이 되는 컴포넌트로, View와 Model과 통신한다.
  • 사용자가 View에서 수행한 작업을 해석하고 이를 통해 Model을 업데이트한다.
  • Model이 가진 데이터가 변경되면 View를 업데이트 해준다.
  • 애플리케이션의 생명주기를 관리한다.

MVC 패턴의 장점과 단점

장점

  • 간단하다.
  • 다른 아키텍처 패턴에 비해 코드의 양이 적다.
  • 각 컴포넌트들은 분명하게 서로 다른 책임을 가진다.
  • 짧은 시간 내에 간단한 애플리케이션을 만들 때 유용하다.

단점

MVC 패턴의 주요한 단점들은 Controller에 있는데, 이 이유는 다음과 같다.

  • Controller와 View 및 Model의 결합도가 아주 높다. → 재사용성이 줄어든다.
  • UIViewController 클래스가 Controller 역할을 하는데, UIViewController는 View와 Controller가 너무 가까이 존재해서 책임의 분리가 잘 이루어지지 않게 된다.
    • View와 Controller 사이의 결합도가 높아지면 View의 개입 없이 Controller를 독립적으로 테스트하는 것이 어려워진다.
  • Controller의 역할에서 벗어나는 책임을 부여하는 경우 등 Controller를 오버로드하는 경향이 높다.
    • 예: 비즈니스 로직의 일부 추가, 테이블의 데이터 소스나 내비게이션 등의 역할을 위임받는 경우 등
    • Massive View Controller가 될 가능성이 크다.

이런 문제들을 해결하기 위해 view와 관련된 코드를 다른 클래스로 옮기고, view controller 간의 내비게이션을 coordinator로 분리하거나 UITableViewController나 UICollectionView의 Delegate와 data source를 view controller 외부로 전달할 수 있다.

MVC 패턴 적용해보기

이 부분은 실제 코딩이랑 관련된 내용들이라… 흥미롭거나 기억하고 싶은 부분들만 적어둬서 글이 매끄럽지 않을 수 있습니다 😅

MVC Layers

Model-View-Controller 그대로 폴더를 생성한다.

Model

  • Models
  • Services
    • 데이터를 주고받는 클래스들
    • 관련 프로토콜을 정의하고 Service 클래스들이 이를 채택하도록 한다.
  • Extensions
  • Constants
    • 앱 내에서 쓰이는 상수 파라미터들 포함
    • 예: 아이콘의 이름들

View

UI 관련, 사용자와의 상호작용이 일어나는 인터페이스

  • 버튼과 같은 간단한 view들은 앱 내에서 재사용한다.
  • 복잡한 뷰들은 더 간단한 view들로 구성되어 있다.

Controller

UIViewController의 하위 클래스로, 앱의 주요 부분이며 model과 view를 연결한다.


Information Flow

MVC 패턴에서 어떻게 정보를 전달할 수 있을까?

컨트롤러는 뷰와 모델에 대한 레퍼런스(인스턴스)를 포함한다. 따라서 컨트롤러는 뷰와 모델의 public 메소드를 사용해서 정보를 바로 전달할 수 있다.

let tasksLists = tasksListService.fetchLists()
let view = HomeView()
view.setLists(taskLists)

Delegate Pattern

그럼 사용자가 View와 상호작용한 후에는 이를 컨트롤러에 어떻게 전달할 수 있을까?

View는 메소드를 호출할 컨트롤러 인스턴스를 가지고 있지 않기 때문에 Delegate 패턴을 사용해야 한다.

Delegate 패턴은 클래스가 다른 클래스의 인스턴스의 책임을 대신 가질 수 있도록 한다. 사용자의 상호작용에 대한 동작은 Controller에서 구현된다.

AppDelegate and SceneDelegate

iOS 13 이후 AppDelegate의 일부 역할이 SceneDelegate로 옮겨졌다. AppDelegate는 앱의 생명주기와 관련 설정을 담당하고, SceneDelegate는 화면에서 무엇이 보여지는지와 어떻게 보여지는지를 담당한다.

SceneDelegate에 앱의 초기 화면을 띄우는 코드를 작성한다.


ViewController

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
    }
    ...
}

View

  • Controller로부터 데이터를 전달받으면 View를 업데이트하는 메소드를 구현한다.
    func setTasksLists(_ lists: [TasksListModel]) {
        tasksList = lists
        tableView.reloadData()
        emptyState.isHidden = tasksList.count > 0
    }
  • delegate를 사용해서 사용자의 상호작용을 컨트롤러에게 전달해야 한다.
    • View 클래스 내에 delegate 프로퍼티를 추가한 후, ViewController 코드 내에서 view의 Delegate를 자기 자신으로 설정한다.

Testing

좋은 아키텍처의 특징 중 하나는 테스트가 가능하다는 것이다.

테스트의 원칙

테스트 코드를 작성할 땐 FIRST라는 기준을 고려해야 한다.

  • Fast
  • Independent
  • Repeatable
  • Self-validating
  • Timely
    • 테스트는 실제 코드가 작성되기 전에 작성되어야 한다. 이를 테스트 주도 개발(TDD, Test-Driven Development)이라고 한다.

테스트 코드 작성해보기

  • setUpWithError()
  • tearDownWithError()
  • 먼저 테스트 타겟이 테스트 할 클래스를 볼 수 있어야 한다. 따라서 프로젝트를 import한다.
    @testable import MVC_MyToDos
  • 테스트할 클래스의 변수를 추가한다.
    var sut: HomeView!
    • sut는 system under test 의 약자이다.
  • 테스트를 생성한다.
    testViewLoaded_whenViewIsInstantiated_shouldBeComponents
    • Apple 프레임워크가 실행할 테스트를 감지하기 위해서는 함수명이 test 로 시작해야 한다.
    • 테스트 함수의 이름은 테스트 할 함수 이름_어떤 상황인지_무슨 결과를 얻어야 하는지 가 드러나도록 지으면 좋다.

private으로 선언한 컴포넌트들은 클래스 외부에서 접근해서 읽을 수는 있지만 수정할 수는 없다. → 테스트 가능

Helper 클래스

  • InMemoryCoreDataManager
    • CoreDataManager와 같은 기능을 하지만 데이터베이스가 메모리 상에서 생성되고 테스트가 끝나면 사라진다.
  • MockNavigationController
    • 내비게이션이 호출(push 또는 pop)되는 시점을 알 수 있도록 특별한 변수를 생성한다.

Mocking Services

  • MockTaskListService
  • MockTaskService

두 클래스들은 기존 클래스의 메소드를 오버라이딩하지만 데이터베이스에 접근하지 않는다.

Controller 테스트

컨트롤러의 테스트는 Model과의 상호작용과 다른 화면으로의 내비게이션에 중점을 둔다.

MockTasksListSerivce와 MockTaskService 클래스를 통해 의존성 주입이 테스트를 얼마나 편하게 만드는지 알 수 있다.

View 테스트

서로 다른 컴포넌트들이 알맞은 정보를 보여주고 있는지, 사용자의 상호작용이 예상한 결과를 만들어내는지를 테스트한다.


✏️ 느낀 점

  • 항상 MVC 패턴을 공부할 때마다 각 구성요소들의 역할이 추상적으로만 느껴졌는데, 이렇게 구체적으로 개념과 구현 방법을 정리하니까 훨씬 이해가 잘 된다.
  • 특히 UI와 관련된 요소는 UIView 클래스에서 모두 구현하니까 ViewController의 역할도 줄어들고, 각 구성요소들의 역할이 더욱 명확해지는 것 같다.
  • Model, View, Controller 각각에서 어떤 것들을 테스트하는지도 알게 되었다. View의 동작도 테스트할 수 있다는 것을 처음 알게 되어서 신기했다. 시간이 된다면 꼭 책에서 나온 것처럼 테스트를 세밀하게 해보고 싶다.
    • TDD에도 관심이 생겼다! 알아야 할 게 너무 많구만 😂
  • iOS에서 의존성 주입을 어떻게 사용하는지도 처음 알게 되었다. 이 부분은 사실 아직 완전히 와닿지는 않긴 한데 다른 아키텍처들도 쭉 공부하다 보면 자연스럽게 이해될 수 있을 거라고 믿는다. ㅎㅎ

+) 적어도 MVVM까지는 쭉 읽어볼까 했었는데.. 아직 MVC 패턴도 완전히 체화되지 않은 상태라서 졸업프로젝트에서 책에 나온 MVC 패턴 적용 방법을 그대로 따라해본 다음 MVC 패턴에 익숙해지면 그 다음 아키텍처 패턴도 공부해 볼 예정이다.

profile
나의 내일은 파래 🐳

0개의 댓글