본문 바로가기
개린이 이야기

MVVM(Model-View-ViewModel)에 관하여

by iOS 개린이 2023. 7. 12.

탄생 배경

대형 프로젝트는 수 십개의 화면, 수만 개의 코드 라인으로 구성될 수 있다. 이런 프로젝트의 복잡성을 관리하지 못하면 코드의 유지보수가 어려워지고, 버그가 발생하기 쉬워진다.

복잡성을 관리하기 위한 방법 중 하나로 패턴이 등장했다.

앱에 역할과 책임을 나누어 기능을 분리하고, 각 기능이 서로에게 미치는 영향을 최소화하고, 각 부분을 독립적으로 유지 관리하고 테스트할 수 있도록 하는 것이다.

 

최초의 디자인 패턴 중 하나로 MVC 패턴이 등장한다.

MVC는 모델, 뷰, 컨트롤러의 세 가지 요소로 앱을 분리하여 코드의 구조를 단순화하고 유지관리를 용이하게 했다.

하지만 MVC에는 단점이 존재한다.

 

1. Massive ViewController

복잡한 앱에서는 Controller가 방대해지는 경향이 있다.

왜냐하면 Controller는 View와 Model 사이에서 주고받는 역할을 담당하면서, 데이터 변환, 애니메이션 처리, 네트워킹 처리 등의 작업을 수행하기 때문이다.

따라서 방대한 Controller의 코드는 많은 종속성을 가질 수도 있고, 가독성과 유지보수성이 떨어진다.

 

2. View와 ViewController의 밀접함

Controller와 View는 사용자 인터페이스와 사용자와의 상호작용을 처리하는 데 필요한 부분이기 때문에 밀접한 관계를 가진다. 사용자가 View에서 특정 액션을 취하면, 그 정보는 Controller를 통해 Model로 전달된다. 또한 Controller는 사용자의 입력을 받아 처리하고, 그에 맞게 View를 업데이트한다.

 

이런 밀접한 관계로 인해 서로에게 강하게 종속되며, 이것은 재사용성과 유지보수성을 떨어뜨린다.

패턴은 각 기능이 서로에게 미치는 영향을 최소화하고, 독립적으로 만들어 재사용성과 유지보수성을 증가시키기 위함인데, 패턴의 존재에 어긋나는 것이다.

 

이러한 이유로 대형 프로젝트와 같은 복잡한 앱에서는 한계를 보였고, 이것을 해결하기 위해 MVVM이 등장했다.

MVVM은 Model과 View 사이에 ViewModel이라는 새로운 컴포넌트를 도입한다.

ViewModel은 View에 표시하기 위한 데이터를 준비하고, 사용자의 액션을 Model로 전달하는 역할을 담당한다.

ViewModel을 통해 Controller의 역할이 줄어들고, View와 Controller 사이의 의존성도 낮아진다.

 

 

MVVM(Model-View-ViewModel)

1. View

View의 역할

앱의 UI를 담당하는 계층이다.

View의 디자인 부분과 함께 ViewModel로부터 데이터를 받아 사용자에게 보여주는 역할도 한다.

ViewModel에서 데이터를 처리하고, 필요한 형태로 변환한 후에 View에 제공하는 것이다.

예를 들어, ViewModel이 네트워크를 통해 받아온 데이터를 가공해서 View에 제공하면, View는 해당 데이터를 화면에 표시한다.

 

또한, View는 사용자의 액션을 ViewModel에 알려준다. 

예를 들어, 사용자가 버튼을 누르면, 이것을 ViewModel에 전달하고, ViewModel이 처리한다. 이 때, ViewModel은 필요에 따라 Model을 업데이트하거나 다른 동작을 수행한다.

 

정리하면, MVVM에서 View 계층은 사용자 인터페이스와 상호작용을 담당하며, ViewModel로부터 데이터를 받아 화면에 표시하고, 사용자의 입력을 ViewModel에 전달하는 역할을 수행한다.

 

View의 특징

View는 ViewModel과의 데이터 바인딩을 통해 스스로 데이터를 보여준다. (데이터가 보이도록 하는 설정 책임을 View 스스로가 가짐)

여기서 View는 ViewModel에 의존하지만, ViewModel은 View에 대해 알지 못한다. (View와 ViewModel과의 단방향 의존성을 의미함) 이를 통해 View의 재사용성과 유지보수성을 향상시킨다.

View 계층에서는 원하는 로직을 ViewModel에서 골라 사용하면 된다.

 

 

2.  ViewModel

ViewModel의 역할

데이터 변환: ViewModel은 Model로부터 데이터를 가져와 View에서 사용할 수 있는 형태로 가공/처리 한다.

예를 들어, Model이 가지고 있는 날짜 데이터를 ViewModel에서 사용자가 알아볼 수 있는 'YYYY-MM-DD' 형태로 변환하는 것이다.

 

뷰 로직 처리: ViewModel은 View에 대한 로직을 처리한다. 예를 들어, View에서 액션이 들어왔을 때, 액션에 따라 어떤 View를 보여줄 지 등의 로직을 ViewModel이 담당한다.  

 

간단하게 View의 요청에 따라 로직을 실행하고, Model의 변화에 따라 View에 전달한다.

 

ViewModel의 특징

View 계층에서도 설명했지만, ViewModel은 View에 대해 알지 못한다. 

비즈니스 로직만을 가지고 있는 깔끔한 계층으로 UI관련 코드와 완전히 분리되어 있으며, 따라서 UIKit과 같은 프레임워크를 import할 필요가 없다.

 

Model이 변경되면 View도 변경될 수 있도록 View와 ViewModel 간의 데이터 바인딩을 사용한다.(데이터의 일관성 유지)

 

 

3. Model

Model의 역할

MVVM에서 Model 계층은 MVC에서 Model 계층의 역할과 동일하다.

데이터 관리: 앱의 핵심 데이터(저장할 데이터, 네트워크를 통해 가져오는 데이터, 사용자가 입력하는 데이터 등)를 관리한다.

 

상태 변경 알림: Model에서 상태가 변경되었음을 ViewModel에 알린다. 이 정보를 바탕으로 ViewModel에서 필요한 액션을 취하고, 변경사항을 View에 반영한다.

 

Model의 특징

독립적인 계층으로 UI나 사용자 입력에 대한 정보를 전혀 알지 못한다. 이로 인해 재사용성과 유지보수성이 높다.

 

 

Model과 ViewModel 모두 앱의 비즈니스 로직을 처리한다고 하는데 무엇이 맞는말이지?

둘 다 비즈니스 로직을 처리하는 것이 맞지만, 서로 다른 유형의 '비즈니스 로직'을 가리킨다.

 

Model에서는 앱의 '도메인 로직'을 수행한다. 도메인 로직이란, 앱의 핵심 기능을 구현하는 로직을 의미한다. 이는 데이터를 데이터 베이스에 저장하거나, API로부터 데이터를 가져오는 등의 작업을 말한다.

 

ViewModel에서는 앱의 '프레젠테이션 로직'을 수행한다. 프레젠테이션 로직이란, Model의 데이터를 View가 사용할 수 있는 형태로 변환하는 등의 작업을 말한다.

 

즉, Model은 데이터의 CRUD 작업과 같은 핵심적인 데이터 처리와 관련된 비즈니스 로직을 담당하고, ViewModel은 Model의 데이터를 사용자 인터페이스에 적합하게 가공하는 등의 UI와 관련된 비즈니스 로직을 담당한다.

 

 

MVC와 MVVM

1. UIKit(MVC)과 SwiftUI(MVVM)

UIKit에서, 화면의 주요 구성 단위는 'UIViewController' 이다. 각 컨트롤러는 일반적으로 하나의 화면을 담당하며, 사용자 인터페이스를 관리하고, 사용자 입력에 대응하는 로직을 구현한다.

 

반면에, SwiftUI에서 화면의 주요 구성 단위는 'View'이다. 각 뷰에서 보여지는 UI를 주도한다.

 

2. Controller와 ViewModel

MVC에서는 사용자와의 상호작용이 일어났을 때, View에서 Target-Action이나 Delegate 패턴을 통해 Controller로 전달했고, Controller에서 어떤 작업을 실행할 지 결정했었다.

 

반면에, MVVM에서는 사용자와 상호작용이 일어났을 경우, 데이터 바인딩으로 인해 View에서 일어나는 행동에 따라 ViewModel의 상태가 변하고, 그 변화가 다시 View에 반영된다. ViewModel은 로직만 가지고 있고, View에 대해 알지 못하기 때문에 View에서 데이터 바인딩을 통해 어떤 작업을 실행할 지 결정한다.

 

3. 데이터 변경

MVC에서는 Model에서 데이터가 변경되면, 그 변경 사항을 Controller에게 알린다.

Controller에서는 해당 변경을 바탕으로 View을 어떻게 업데이트할 지 결정하고 명령을 내린다.

 

MVVM에서는 Model에서 데이터가 변경되면, 그 변경사항을 ViewModel에게 알린다.

ViewModel은 해당 변경을 바탕으로 자신의 상태가 업데이트되고, 데이터 바인딩을 통해 View에서 캐치하여 작업을 수행한다. 

 

 

UIKit과 MVVM 예시

Model 정의

struct User {
    let name: String
    let age: Int
}

 

ViewModel 정의

class UserViewModel {
    var user: User? {
        didSet {
            self.bindUserViewModelToController?()
        }
    }

    var bindUserViewModelToController: (() -> ())?

    func updateUser() {
        let user = User(name: "개린이", age: 23) //Model의 값 변경.
        self.user = user
    }
}

 

옵저버 프로퍼티 'user'를 통해 Model 데이터(User)에 값이 설정될 때마다, 'didSet' 이 발생하면서 'bindUserViewModelToContoller' 메서드를 실행시킨다. 

 

'bindUserViewModelToContoller' 는 클로저를 옵셔널로 선언한 프로퍼티로 뷰 계층인 ViewController에서 설정이 되며, ViewModel에서 데이터가 변경되었을 때, 알림을 받는다.

 

'updateUser' 메서드는 Model 계층의 데이터를 업데이트 하는 메서드이다.

'User' 데이터를 생성하고, 'self.user' 에 할당하여 'didSet'이 호출되도록 한다.

예시일뿐, 일반적인 상황에서는 네트워킹을 통해 데이터를 가져오는 코드를 작성하겠죠?

 

 

View 정의

class ViewController: UIViewController {

    var viewModel = UserViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //클로저를 통해 데이터 변경 알림 받기
        
        viewModel.bindUserViewModelToController = {
            // ViewModel의 데이터가 변경되었을 때 수행할 작업
            if let user = self.viewModel.user {
                print("User name: \(user.name), age: \(user.age)")
            }
        }

        viewModel.updateUser() //데이터 업데이트 시작
    }
}

 

1. 'UserViewModel' 인스턴스를 생성하여 'viewModel' 프로퍼티에 할당한다. 이를 통해 뷰모델을 통해 데이터를 받아올 수 있도록 한다.

 

2. 뷰 모델의 'bindUserViewModelToController' 클로저를 설정하여 ViewModel에서 데이터 변경 알림을 보냈을 때, 호출되도록 한다. 호출되는 내용은 뷰 모델에서 업데이트 한 'user' 객체의 'name' 과 'age' 값을 출력한다. 

 

3. 뷰 모델의 'updateUser' 메소드를 호출하여 데이터를 업데이트하고, 설정한 클로저가 호출된다.

 

정리

1. UIKit에서 MVVM을 사용하면 ViewController가 View 계층에 해당한다. ViewController는 ViewModel을 소유하고 있는 반면, ViewModel은 ViewController에 대한 종속성이 제로다. 

 

2. 클로저를 이용한 데이터 바인딩을 통해 ViewModel에서 데이터가 변경되었을 때, ViewController에게 변경사항을 알려준다.

 

3. ViewModel이 데이터 처리를 담당하게 되면서 뷰의 역할이 사용자 인터페이스의 표현과 입력 처리로 단순화된다. 즉,  뷰는 UI를 그리는데 집중하고, 뷰모델은 UI로직을 처리하는 데 집중함으로써, MVC에서 컨트롤러가 뷰의 설정 책임을 맡는 것과 다르게, ViewModel은 뷰의 설정 책임에서 자유롭다. (명확한 책임 분리)

 

 

 

MVVM의 단점

1. 복잡성

MVVM은 MVC에 비해 추가적인 계층 ViewModel이 필요하며, 이로 인해 코드의 복잡성이 증가할 수 있다.

소규모의 프로젝트에서는 MVVM이 오히려 과도한 패턴의 사용이 될 수 있다.

 

2. 디버깅

View와 ViewModel간의 데이터 바인딩을 통한 통신은 아주 중요한 역할을 하면서 강력한 도구이지만, 잘못된 사용은 성능 문제를 가져올 수 있고, 디버깅이 어려워질 수 있다.

 

3. Massive ViewModel

MVC 패턴의 한계점인 'Massive Controller' 와 동일하게 MVVM에서도'프레젠테이션 로직'이 늘어나고, 단일책임 원칙이 지켜지지 않을 경우, ViewModel이 거대해 질 수 있다. 책임에 맞는 분리와 로직을 적절하게 설계하는 것이 중요하다.

 

아직 MVVM을 직접 사용해보지 못해서 단점이 와닿지 않는다. 직접 MVVM을 사용하여 프로젝트를 경험해보고, 추가적으로 작성해야할 것 같다.

 

 

 

 

Reference

http://yoonbumtae.com/?p=4215

https://velog.io/@ictechgy/MVVM-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

https://leemyungjic.tistory.com/24

 

'개린이 이야기' 카테고리의 다른 글

Downsampling(다운샘플링)에 관하여  (0) 2024.01.20
이미지 캐싱에 관하여  (0) 2024.01.18
Git Flow에 관하여  (0) 2023.06.26
Git에 관하여  (0) 2023.06.25
Swift의 API Design Guidelines  (0) 2023.06.12