MVC란?

MVC란 화면(visual components)으로 인터페이스가 구성되어있는 소프트웨어에서 사용되는 디자인 패턴으로 주요한 목적은 소프트웨어의 비즈니스 로직과 화면을 표현하는데 관련된 프리젠테이션 로직을 구분하는데에 있다. 같은 목적을 가진 패턴으로 MVVM, MVP, MVI등이 있는데 모두 그 기반은 MVC에 두고 있다.

 

Apple MVC

먼저 애플의 공식 document에서 이야기하는 MVC 패턴에 대해 살펴보자.

MVC

 

개요

MVC 디자인 패턴은 어플리케이션을 3개의 역할을 가진 오브젝트로 나눈다. Model, View, Controller. MVC 패턴은 각 오브젝트의 역할 뿐만아니라 오브젝트들이 서로 소통하는 방식도 같이 정의한다. 3개의 각각의 오브젝트들은 추상적인 경계에 의해 분리되어있고, 그 경계를 넘어 다른 타입의 오브젝트들과 소통하게 된다. 이러한 MVC 오브젝트들의 집합은 때때로 layer(계층, 단)라고 불려지기도 한다. e.g) Model 레이어, View 레이어

MVC는 대다수의 Cocoa 어플리케이션에 있어 좋은 설계이다. 이 패턴을 채택함으로써 얻는 이득은 많다. 어플리케이션의 많은 오브젝트들이 좀 더 재사용가능해지고, 그들의 인터페이스가 좀 더 잘 정의될 수 있다. 게다가, 많은 Cocoa 기술과 설계구조가 MVC 기반이고, 당신이 만든 사용자 오브젝트들이 MVC 역할 중의 하나를 수행하도록 요구한다.

 

1. Model

모델은 어플리케이션에 필요한 데이터를 캡슐화 하고 그 데이터를 조작하고 처리하는 로직과 계산을 정의한다. 예를들면, 모델은 게임의 캐릭터나 전화번호부의 연락처를 나타낸다. 모델은 다른 모델 혹은 모델들과 관계를 맺을 수 있고 때때로 모델 계층은 하나나 그 이상의 오브젝트 그래프가 될 수도 있다. 대부분의 데이터 - 어플리케이션의 영속적인 상태의 일부 (파일이나 데이터베이스에 저장된) - 는 에플리케이션으로 로드 된 후 모델 오브젝트에 위치해야 한다. 모델은 도메인의 특정한 문제와 관련된 지식과 해결책이므로, 유사한 문제에 재사용 될 수 있어야 한다. 모델은 그 데이터를 표현하고 사용자로 하여금 데이터를 수정할 수 있게 하는 view 오브젝트와는 명시적인 연결이 없어야 한다.(유저 인터페이스와 프리젠테이션과는 연결점이 없어야 한다.)

  • 커뮤니케이션: 데이터를 생성하거나 수정하는 뷰 계층에서의 사용자 액션은 컨트롤러 오브젝트를 통해 소통하고 결과적으로 모델 오브젝트를 생성하거나 업데이트 하게된다. 모델 오브젝트가 변경될 때(예를들어, 새로운 데이터가 네트워크를 통해 수신되었을 때), 모델은 컨트롤러 오브젝트에 이를 알리고 컨트롤러 오브젝트가 적절한 view의 업데이트를 진행하게 된다.

 

2. View

뷰 오브젝트는 어플리케이션에서 유저가 볼수 있는 오브젝트이다. 뷰는 자기자신이 어떻게 그려야 할지를 알고있고, 사용자 액션에 응답할 수 있다. 뷰 오브젝트의 주요한 목적은 어플리케이션의 모델에 있는 데이터를 디스플레이하고 데이터의 수정을 가능하게 하는것이다. 그럼에도, MVC에서 Model은 View와 decoupled되어야 한다.

  • 커뮤니케이션: 뷰 오브젝트는 컨트롤러를 통해 모델의 변화를 알아내고 사용자 만들어낸 변화를(e.g 텍스트 필드에 텍스트를 작성하는 것) 컨트롤러를 통해 모델 오브젝트에 전달한다.

 

3. Controller

컨트롤러 오브젝트는 뷰오브젝트와 모델 오브젝트 사이에서 마치 중재자처럼 행동한다. 컨트롤러 오브젝트는 모델의 변화를 뷰가 알수있고, 뷰의 변화를 모델이 알수있는 통로이다. 또한 컨트롤러는 애플리케이션을 위해 setup과 작업을 조정하는 역할을 수행하고 다른 오브젝트들의 라이프사이클을 관리한다.

  • 커뮤니케이션: 컨트롤러는 뷰에서 만들어진 유저 액션을 해석하고 새로운 데이터 혹은 수정된 데이터를 모델 레이어에 전달한다. 모델이 변경되면, 컨트롤러는 뷰 오브젝트가 디스플레이 할 수 있도록 데이터를 전달한다.

 

Traditional MVC와의 비교

위에서 살펴본 애플의 Cocoa MVC와 전통적인 MVC 방식은 차이가 있는데, 그걸 살펴보자.

 

Traditional MVC의 개요

Traditional MVC

 

전통적인 MVC 패턴은 아래와 같은 3종류의 패턴으로 구성되어 있다.

  • Composite
    • 어플리케이션의 뷰는 nested뷰들의 협력으로 만들어진 view hierarchy이다. 이러한 디스플레이 컴포넌트들은 버튼과 같이 독립적인 뷰 부터 테이블 뷰 같은 합성 뷰에까지 다양한 종류를 아우른다. 모든 수준의 합성 뷰 구조에서 사용자 액션의 input을 받아들이고 display 하는 기능을 수행할 수 있다.
  • Startegy
    • 컨트롤러는 하나나 그 이상의 뷰 오브젝트에 대한 전략을 수행한다. 뷰는 자기 자신의 기능을 visual 영역으로 국한하고, 컨트롤러를 대리자로 삼아 결정을 넘긴다.
  • Observer
    • 모델은 어플리케이션 내부의 오브젝트들(특히 뷰 오브젝트)에게, 상태 변화에 대해서 notify한다.

사용자가 뷰를 조작하면 이벤트가 발생한다. 컨트롤러는 그 이벤트를 수신해 특정한 방법으로 해석하여 strategy를 적용한다. 이 strategy는 메시지를 통한 모델의 상태 변경 요청일 수도 있고, 뷰의 행동과 상태를 변화하는 요청일 수도 있다. 모델은 옵저버로 등록한 모든 옵저버들에게 자신의 상태변화를 notify한다. 만약 옵저버가 뷰 오브젝트라면, 뷰의 상태를 업데이트 하게 된다.

 

Apple의 생각

전통적인 MVC 방식은 코코아 버전의 MVC와 상당히 유사한점이 있고, 또 전통적인 방식으로도 무리없이 애플리케이션을 만들 수 있다. 바인딩 기술을 사용하면, 뷰가 모델 오브젝트를 직접 관찰하여 상태변화의 알림을 쉽게 받을 수 있다.

다만, 이러한 방식은 이론적인 문제가 있다. 위에서 살펴봤듯이 뷰 오브젝트와 모델 오브젝트는 어플리케이션에서 가장 많이 재사용 되는 오브젝트들이다. 이러한 오브젝트들의 재사용성을 높이기 위해서는 설계 측면에서 모델과 뷰 오브젝트들을 서로 분리하여 유지하는게 좋다.

대부분의 코코아 어플리케이션들은, 모델 오브젝트의 상태변화를 컨트롤러를 통해서 뷰 오브젝트에 전달한다. 위 그림이 나타내는 Cocoa version MVC는 2가지의 디자인패턴을 추가해서 좀 더 깔끔한 설계를 보여주고 있다.

Apple's Cocoa MVC

기존의 Strategy 패턴에 더해서 Mediator(매개자) 패턴을 통합했다. 이를 통해 모델과 뷰 오브젝트 사이의 데이터 흐름을 양방향으로 중개한다. 모델의 상태변경은 애플리케이션의 컨트롤러 오브젝트를 통해서 뷰로 전달된다. View 오브젝트의 경우 Command 패턴을 통합하여 target-action 매커니즘을 사용한다.

위에서 이야기 했듯 전통적인 MVC는 View가 Model을 관찰대상으로 등록을 하고, Model의 변화가 있을 때 등록된 관찰자(Observer)들에게 알리는 역할을 하므로 Model이 View를 알게된다.(의존성이 생기게 됨)

즉 Cocoa MVC의 핵심 콘셉트는 view와 model 오브젝트들의 재사용성을 향상을 목적으로 view - model간 의존성을 제거하기 위해 Controller가 상호 소통의 매개체 역할을 수행하는 것이 골자이다. 또한, View와 Model은 재사용 되어야 하므로 특정 오브젝트에 종속되어서는 안되되기 때문에 서로 다른 영역의 오브젝트들을 알고있지 않아야 한다. 뷰의 경우 사용자 액션을 Controller로 알리기 위해서는 연결관계가 필요한데, 이러한 구현을 UIKit View 컴포넌트들에서 흔히 볼 수 있는 delegate와 data source라고 명명되는 Protocol들을 통한 의존성 역전으로 해결했다. 마찬가지로 Controller가 Model의 변화를 감지하기 위해서 Model이 Controller를 직접 알고 있는 것이 아니고 옵저버 패턴이나 딜리게이트 패턴을 사용하게 된다.

두 버전의 MVC 도식을 비교해 보면 애플 버전의 MVC에서는 View와 Model 사이에 어떠한 소통도 하지 않는 것을 볼 수 있다.

 

Apple MVC의 한계

Realistic Cocoa's MVC

실제로 MVC 구조로 iOS 개발을 해보면 금방 인식하게 되는 문제점이 있다. Controller, 즉 View Controller가 View Life Cycle에 깊게 연관되어 있기 때문에 View와 Controller가 분리되어있다고 보기 어렵다는 것이다. 앱의 핵심 비즈니스 로직들은 Model 계층에 분리할 수 있지만, 프리젠테이션 로직들은 View Controller에서 떨어져 존재하기가 힘들기 때문이다.

이로 인해 발생할 수 있는 문제는 아래와 같다.

 

1. Massive한 View Controller

View와 Controller가 많은 역할을 하므로 코드의 양이 많아진다. 다만 이러한 점은 MVVM과 같은 패턴을 사용해도 앱이 커지게 되면 ViewModel이 비대해지는 일이 생길 수도 있으므로 앱 사이즈가 엄청나게 커지지만 않는다면 크리티컬한 단점은 아니라고 생각한다. MVC도 역할분리를 통해 충분히 VC의 사이즈를 줄일 수 있다고 생각한다. 다만, 위에서 이야기 했듯 VC가 View Life Cycle에 깊게 연관되어 있기 때문에 MVVM 패턴을 사용하면 그 연관된 부분을 View 계층으로 따로 떼어내는 효과가 있을 것이다.

 

2. 역할에 따른 코드 분리의 어려움

1번과 거의 유사한 문제라고도 볼 수 있는데, VC가 프리젠테이션 로직과 더불어 View를 그리는 코드까지 구현하므로 한 객체에서 여러 일을 수행하게 되어 역할에 따른 코드 분리가 어려워지게 된다.

 

3. Test의 어려움

View와 Controller가 완벽하게 분리되어있지 않으므로, Model을 제외하면 Unit Test가 어려워지게 되어 Testable한 코드를 작성하기가 어렵게 된다.

다음에는 이러한 단점을 보완하기 위해 나온 MVVM 패턴을 주제로 이야기해보자.

 

Reference

'Methodology > Architecture' 카테고리의 다른 글

[Architecture] Clean Architecture with iOS  (0) 2022.11.11
[Architecture] MVVM 패턴  (0) 2022.10.30