MVVM 디자인 패턴 알아보기 🧑🏻‍💻

MVVM 디자인 패턴이란?

MVVM은 Model , View , ViewModel 로 나눠져 관리하는 디자인 패턴이다. 화면을 만드는 코드와 데이터를 처리하는 코드를 분리하는 것이 MVVM의 핵심이며, 데이터를 전달하는 방식으로 View가 ViewModel 값을 관찰하여 변화를 반영하게 된다.

 

버튼을 클릭했을 때 이미지가 바뀌는 동작을 구현하려고 할때 MVC 패턴은 버튼이 눌리면 이미지를 바꾼다는 개념이고 MVVM 패턴은 버튼을 누르면 ViewModel의 데이터가 바뀌고 데이터가 바뀌니 이미지도 자연스럽게 바뀐다는 개념이다.

 

이를 토대로 View가 ViewModel의 값을 관찰한다는 말이고 이를 도와주는 프레임워크가 Combine , 라이브러리가 RxSwift이다.

 

Model

간단한 예시를 바탕으로 MVVM을 알아가 보자!

먼저 MVVM의 Model은 데이터 구조만 정의하면 된다. 이 부분은 MVC와 비슷한 부분인 것 같다.

import Foundation

struct MusicData: Codable {
    let resultCount: Int
    let results: [Music]
}

struct Music: Codable {
    let songName: String?
    let artistName: String?
    let albumName: String?
    let imageUrl: String?

    enum CodingKeys: String, CodingKey {
        case songName = "trackName"
        case artistName
        case albumName = "collectionName"
        case imageUrl = "artworkUrl100"
    }
}

enum NetworkError: Error {
    case networkingError
    case dataError
    case parseError
}

Model은 데이터 구조를 정의하는 것 외에 다른 내용은 신경 쓸 필요 없다.

 

View

MVVM의 View는 사용자와의 상호작용을 통해 이벤트가 일어나면 ViewModel에게 이벤트를 알리고 ViewModel이 업데이트 요청한 데이터를 보여준다.

class ViewController: UIViewController {

    @IBOutlet weak var albumNameLabel: UILabel!
    @IBOutlet weak var songNameLabel: UILabel!
    @IBOutlet weak var artistNameLabel: UILabel!
    
    var viewModel: MusicViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 뷰모델에 접근하기 전에 무조건 필요 (생성)
        self.viewModel = MusicViewModel()
        
        // 데이터 변경이 완료된 후, 클로저에서 어떤 일을 할지 정의 (할당)
        self.viewModel.onCompleted = { [weak self] _ in
            DispatchQueue.main.async {
                self?.albumNameLabel.text = self?.viewModel.albumNameString
                self?.songNameLabel.text = self?.viewModel.songNameString
                self?.artistNameLabel.text = self?.viewModel.artistNameString
            }
        }
    }
    
    @IBAction func startButtonTapped(_ sender: UIButton) {
        viewModel.handleButtonTapped()
    }
    
    @IBAction func nextButtonTapped(_ sender: UIButton) {
        
        guard viewModel.music != nil else { return }
        
        // 원칙 ========================================
        //다음 화면의 뷰컨트롤러가 가져야 하는 뷰모델 ⭐️
        let detailVM = DetailViewModel()
        
        //뷰모델이 가져야 하는 데이터 ⭐️
        detailVM.music = viewModel.music
        detailVM.imageURL = viewModel.music?.imageUrl
        
        
        // 일반적으로 ====================================
        //let detailVM = viewModel.getDetailViewModel()
        
        //다음 화면 뷰컨트롤러 ⭐️
        let detailVC = storyboard?.instantiateViewController(withIdentifier: "detailVC") as! DetailViewController
        //뷰모델 전달 ⭐️ (뷰디드로드가 호출되기 전에 전달)
        detailVC.viewModel = detailVM
        

        
        detailVC.modalPresentationStyle = .fullScreen
        self.present(detailVC, animated: true)
    }
}

self.viewModel = MusicViewModel() 을 통해 ViewModel에서 데이터를 가져와야한다.

@IBAction func nextButtonTapped(_ sender: UIButton) {
        
        guard viewModel.music != nil else { return }
        
        // 원칙 ========================================
        //다음 화면의 뷰컨트롤러가 가져야 하는 뷰모델 ⭐️
        let detailVM = DetailViewModel()
        
        //뷰모델이 가져야 하는 데이터 ⭐️
        detailVM.music = viewModel.music
        detailVM.imageURL = viewModel.music?.imageUrl
        
        
        // 일반적으로 ====================================
        //let detailVM = viewModel.getDetailViewModel()
        
        //다음 화면 뷰컨트롤러 ⭐️
        let detailVC = storyboard?.instantiateViewController(withIdentifier: "detailVC") as! DetailViewController
        //뷰모델 전달 ⭐️ (뷰디드로드가 호출되기 전에 전달)
        detailVC.viewModel = detailVM
        

        
        detailVC.modalPresentationStyle = .fullScreen
        self.present(detailVC, animated: true)
    }

이와 같이 View에서 버튼을 눌러 다음 화면으로 넘어갈때 데이터가 그 다음 화면에도 사용해야한다면 해당하는 데이터까지 같이 다음 VC의 ViewModel에 데이터를 전달해주어야 한다.

 

이와 같이 MVVM에서는 VC가 View 역할을 하고 디자인적인 요소 뿐 만 아니라 ViewModel로부터 데이터를 가져오고 ViewModel에서 어떤 메서드를 이용할지에 대해서도 구현되어야 한다.

 

ViewModel

MVVM의 ViewModel은 앱의 핵심적인 비즈니스 로직을 가지고 있다. 사용자와의 상호작용을 View가 보내주면 그에 맞는 이벤트를 처리하고 Model의 데이터 CRUD를 진행한다.

 

ViewModel의 코드 구성을 하나씩 살펴보자

class MusicViewModel {
    
    // 핵심 데이터(모델) ⭐️⭐️⭐️ (뷰모델이 가지고 있음)
    var music: Music? {
        didSet {
            onCompleted(music)
        }
    }
    
    var onCompleted: (Music?) -> Void = { _ in }

didSet 옵저버를 사용해서 music 속성이 변화할 때마다 ‘onCompleted’ 클로저를 호출해 데이터를 처리한다.

var albumNameString: String? {
    return music?.albumName
}

var songNameString: String? {
    return music?.songName
}

var artistNameString: String? {
    return music?.artistName
}

View에서 사용할 데이터를 보내줄 수 있는 Output을 만든다.

 

각각의 인스턴스들은 music model에서 가져온 데이터를 반환하고 view에 필요로하는 데이터를 제공한다.

func handleButtonTapped() {
    fetchMusic { [weak self] result in
        switch result {
            case .success(let music):
                self?.music = music
            case .failure(let error):
                switch error {
                    case .dataError:
                        print("데이터 에러")
                    case .networkingError:
                        print("네트워킹 에러")
                    case .parseError:
                        print("파싱 에러")
                }
        }
    }
}

사용자가 버튼을 탭했을때 호출되는 메서드를 만들어준다. 버튼을 누르면 데이터를 가져오는 메서드를 실행한다.

 

이러한 구성을 통해 View에 어떤것이 있던 무엇을 하던 상관없이 ViewModel에서 로직 구현이 가능해지고 이 점이 핵심인 것 같다 🙂

 

MVVM의 장 단점

  • MVVM의 장점

MVVM은 View로직과 비즈니스 로직을 분리해 생산성이 높고 ViewModel에서 View 없이 구현이 가능해 효율적인 개발이 가능하다.

 

의존성이 적기 때문에 테스트가 수월하고 , View와 ViewModel이 1:N 관계로 중복되는 로직을 모듈화해 여러 View에 재사용이 가능하다.

 

  • MVVM의 단점

MVC에 비해 설계가 복잡하고 데이터 전달 개념에 대한 더 깊은 학습을 요하며, 이 때문에 간단한 프로젝트에서는 오히려 MVC보다 생산성이 떨어질 수 있다.

 

ViewModel이 비대해질 수 있는 가능성이 있다.

 

 

출처 : https://jeong9216.tistory.com/519