iOS 아키텍처 MVC와 MVVM 비교하기

이경은·2024년 1월 23일
0

소프트웨어 아키텍처(Architecture) 개념에 대해 이해하고, iOS개발의 대표적인 아키텍처인 MVC와 MVVM에 대해 알아보겠습니다.

아키텍처(Architecture)란?

소프트웨어 시스템 전체의 구조와 조직을 결정하는 프로세스로, 시스템의 기본적인 구조를 설계하고 이를 컴포넌트로 분할하며, 컴포넌트 간의 상호작용 및 데이터 흐름을 정의하는 것을 포함합니다.

소프트웨어 아키텍처는 시스템의 전체적인 모습을 정의하며, 시스템의 기능, 성능, 보안, 확장성, 유지보수성 등과 같은 비기능적인 요구사항에 대한 해결책을 제공합니다.




Apple MVC

  • MVC(Model - View - Controller) 아키텍처는 애플리케이션의 구조를 구성하는 패턴 중 하나입니다.
  • 아래에서 설명하는 MVC 패턴은 Apple의 MVC패턴을 기반으로 설명합니다.

Model

데이터와 데이터를 처리하는 로직을 담당합니다. 애플리케이션의 상태와 동작을 나타내는 데이터를 관리하고, 이 데이터를 조작하거나 업데이트하는데 필요한 비즈니스 로직을 포함합니다.

모델은 주로 데이터 구조체나 클래스로 표현되며, 데이터베이스나 네트워크와 같은 외부데이터 소스와의 상호작용을 담당할 수도 있습니다.

import Foundation

struct Todo {
		var text : String
		var completed : Bool
}

위 코드처럼 Todo(할 일)라는 모델타입을 정의할 수 있는데, 이 모델이 가지는 상태 프로퍼티들을 담을 수 있습니다.

View

사용자 인터페이스를 나타냅니다. 즉, 화면에 표시되는 요소들을 담당합니다. 뷰는 사용자가 볼 수 있는 것들로 구성되어 있고, 주로 컴포넌트들(버튼, 레이블, 이미지 등)을 포함합니다.

뷰는 사용자의 입력(input)을 받아 컨트롤러에 전달하고, 모델의 데이터를 표시할 때 사용됩니다.

View는 Storyboard를 통해 구현한 것이라고 생각하면 좋습니다.

Controller

모델과 뷰 간의 상호작용을 관리합니다.

사용자의 입력을 받아 해당하는 모델을 업데이트하고, 모델의 변경사항을 감지하여 뷰를 업데이트합니다.

즉, 컨트롤러는 뷰와 모델 사이의 중간자 역할을 수행하며, 사용자 입력을 받아 처리하고 그 결과를 화면에 반영합니다.

import UIkit

class ViewController : UIViewController {
		@IBOutlet weak var tableView: UITableView!
		@IBOutlet weak var textField: UITextField!

		var todos: [Todo] = []

		@IBAction func addButtonTapped(_ sender: UIButton) {
				guard let text = textField.text, !text.isEmpty else { return }
				self.addTodo(text: text)
				tableView.reloadData()
				textField.text = ""
		}

		func addTodo(text: String) {
				let newTodo = Todo(text: text, complete: false)
				self.todos.append(newTodo)
		}
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
		func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
				return self.todos.count
		}
		
		func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
				let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell", for: indexPath)
				let todo = self.todos[indexPath.row]
				cell.textLabel?.text = todo.text
				cell.accessoryType = todo.completed?.checkmark : .none
				return cell
		}
}

MVC의 장단점

장점

  • 간단한 구조
    MVC는 소프트웨어를 세 가지의 주요 부분으로 분할하여 구조화하기 때문에 개발자들에게 직관적이고 이해하기 쉬운 구조를 제공합니다.
    Model - View - Controller로 구분하여 각 부분이 서로 독립적으로 작동하면서 전체적인 애플리케이션 로직을 구성합니다.

  • Apple의 Cocoa 프레임워크와 호환성
    Apple은 MVC를 기본적으로 지원하고 Cocoa 프레임워크에서도 널리 사용하고 있습니다.
    iOS 및 macOS 애플리케이션 개발에서 Cocoa 프레임워크는 MVC를 따르며, 이는 Apple의 공식적인 지침을 따르며 애플리케이션을 구축하는데 도움이 됩니다.

단점

  • Massive View Controller(MVC)
    Apple의 MVC 구현에서는 종종 뷰 컨트롤러가 매우 비대해지는 경향이 있습니다.
    이는 하나의 뷰컨트롤러에 많은 역할과 책임이 집중되어 복잡성을 증가시키고 유지보수를 어렵게 만듭니다.

  • 테스트 용이성
    Cocoa MVC에서는 테스트하기 어려운 코드가 뷰 컨트롤러에 집중될 수 있습니다.
    사용자 인터페이스와 비즈니스 로직이 뒤섞여 있어 테스트를 분리하기 어려울 수 있습니다.



MVVM - Architecture

사용자 인터페이스(UI)와 로직을 분리하여 유지보수성을 향상시키고, 확장가능한 애플리케이션을 만들기 위해 Model(데이터), View(UI), ViewModel(비즈니스 로직)의 세 가지 구성요소로 분리하는 아키텍처입니다.


Model - View - ViewModel

Model

데이터와 비즈니스 로직을 담당하는 부분

import Foundation

struct Todo {
		var text: String
		var completed: Bool

		mutating func toggle() {
				self.completed.toggle()
		}
}

ViewModel

Model과 View간의 중간매개체로, View에 표시할 데이터를 가지고 있고, 사용자 입력을 받아 Model에 전달

import Foundation

class TodoViewModel {
		var todos : [Todo] = []
		
		func addTodo(text: String) {
		let newTodo = Todo(text: text, completed: false)
		todos.append(newTodo)
		}

		func toggleTodoCompletion(for index: Int) {
				todos[index].toggle()
		}
}

코드를 보면 todos라는 Todo모델 배열 타입의 값을 가지고 있고, 이 todos, 즉 데이터를 다루는 실질적인 비즈니스 로직이 들어갑니다. 결국 뷰에 데이터를 표시하기 위해서 어떠한 비즈니스 로직들을 처리하고 모델 데이터를 가공하여 뷰에 보여주는 역할을 한다고 보면 좋습니다.

여기에서의 핵심은 뷰모델에서는 비즈니스 로직에 집중하는 것이지 뷰에 데이터를 어떻게 보여줄 것인지와 같은 MVC의 Controller의 역할을 하지 않는 것입니다. 후자는 다음 요소인 뷰에서 담당하고 있습니다.

View

MVVM의 View는, Apple의 MVC와 달리 Storyboard + Controller를 총칭하는 개념으로 사용

import UIKit

class ViewController: UIViewController {
		@IBOutlet weak var tableView: UITableView!
		@IBOutlet weak var textField: UITextField!

		var viewModel = TodoViewModel()

		@IBAction func addButtonTapped(_ sender: UIButton) {
				guard let text = textField.text, !text.isEmpty else { return }
				viewModel.addTodo(text: text)
				tableView.reloadData()
				textField.text = ""
		}
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
		func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
				return viewModel.todos.count
		}

		func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
				let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell", for: indexPath)
				let todo = viewModel.todos[indexPath.row]
				cell.textLabel?.text = todo.text
				cell.accessoryType = todo.completed?.checkmark: .none
				return cell
		}

		func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
				viewModel.toggleTodoCompletion(for: indexPath.row)
				tableView.reloadRows(at: [indexPath], with: .automatic)
		}
}

tableView 구성 시의 메서드들을 살펴보겠습니다.

		func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
				viewModel.toggleTodoCompletion(for: indexPath.row)
				tableView.reloadRows(at: [indexPath], with: .automatic)
		}
}

위 메서드는 tableView의 특정 cell이 선택되었을 때 정해진 로직을 수행하는 메서드입니다. 해당 tableView 메서드 자체는 뷰에서 사용자의 인터렉션을 받습니다. 원래의 MVC라면 데이터의 상태들을 변경하는 로직들도 함께 이뤄졌을텐데, MVVM에서는 그러한 로직은 뷰모델에서 담당합니다. 실제 데이터를 가공하는 비즈니스 로직들은 뷰가 아닌 뷰모델로 역할이 분리된 것입니다.

결국 뷰는 UI를 그려주고 사용자의 인터렉션을 받는 용도로 역할을 제한하는 것이 MVVM과 MVC의 차이라고 할 수 있습니다.



MVVM의 장단점

장점

  • 뷰와 로직의 분리 MVVM은 뷰와 비즈니스 로직을 분리시켜주어 각각의 역할을 명확히 합니다. 이를통해 코드의 가독성이 향상되고, 유지보수가 쉬워집니다.
  • 테스트 용이성 뷰모델은 일반적으로 순수한 비즈니스 로직을 담고있으며, 이로 인해 테스트하기 쉬운 코드가 됩니다. 뷰모델은 UI와 독립적으로 테스트할 수 있어서 테스트 코드 작성이 간편합니다.

단점

  • Learning Curve MVVM은 초기에 이해하기 어려울 수 있습니다. 특히 처음 사용하는 개발자들에게는 데이터 바인딩, 뷰모델 작성 등의 개념을 이해하는데 시간이 걸릴 수 있습니다.
  • 보일러 플레이트 작은 규모의 프로젝트/화면에서는 오히려 ViewModel의 적용이 코드의 복잡성이 늘어나고, 반복적으로 비슷한 형태의 코드가 사용되는 보일러 플레이트의 단점이 드러날 수 있다.


Reference

Apple Document : Model-View-Controller
Wikipedia : Model-View-Controller

0개의 댓글