소프트웨어 아키텍처(Architecture) 개념에 대해 이해하고, iOS개발의 대표적인 아키텍처인 MVC와 MVVM에 대해 알아보겠습니다.
소프트웨어 시스템 전체의 구조와 조직을 결정하는 프로세스로, 시스템의 기본적인 구조를 설계하고 이를 컴포넌트로 분할하며, 컴포넌트 간의 상호작용 및 데이터 흐름을 정의하는 것을 포함합니다.
소프트웨어 아키텍처는 시스템의 전체적인 모습을 정의하며, 시스템의 기능, 성능, 보안, 확장성, 유지보수성 등과 같은 비기능적인 요구사항에 대한 해결책을 제공합니다.
데이터와 데이터를 처리하는 로직을 담당합니다. 애플리케이션의 상태와 동작을 나타내는 데이터를 관리하고, 이 데이터를 조작하거나 업데이트하는데 필요한 비즈니스 로직을 포함합니다.
모델은 주로 데이터 구조체나 클래스로 표현되며, 데이터베이스나 네트워크와 같은 외부데이터 소스와의 상호작용을 담당할 수도 있습니다.
import Foundation
struct Todo {
var text : String
var completed : Bool
}
위 코드처럼 Todo(할 일)라는 모델타입을 정의할 수 있는데, 이 모델이 가지는 상태 프로퍼티들을 담을 수 있습니다.
사용자 인터페이스를 나타냅니다. 즉, 화면에 표시되는 요소들을 담당합니다. 뷰는 사용자가 볼 수 있는 것들로 구성되어 있고, 주로 컴포넌트들(버튼, 레이블, 이미지 등)을 포함합니다.
뷰는 사용자의 입력(input)을 받아 컨트롤러에 전달하고, 모델의 데이터를 표시할 때 사용됩니다.
View는 Storyboard를 통해 구현한 것이라고 생각하면 좋습니다.
모델과 뷰 간의 상호작용을 관리합니다.
사용자의 입력을 받아 해당하는 모델을 업데이트하고, 모델의 변경사항을 감지하여 뷰를 업데이트합니다.
즉, 컨트롤러는 뷰와 모델 사이의 중간자 역할을 수행하며, 사용자 입력을 받아 처리하고 그 결과를 화면에 반영합니다.
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
}
}
사용자 인터페이스(UI)와 로직을 분리하여 유지보수성을 향상시키고, 확장가능한 애플리케이션을 만들기 위해 Model(데이터), View(UI), ViewModel(비즈니스 로직)의 세 가지 구성요소로 분리하는 아키텍처입니다.
데이터와 비즈니스 로직을 담당하는 부분
import Foundation
struct Todo {
var text: String
var completed: Bool
mutating func toggle() {
self.completed.toggle()
}
}
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의 역할을 하지 않는 것입니다. 후자는 다음 요소인 뷰에서 담당하고 있습니다.
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의 차이라고 할 수 있습니다.
Apple Document : Model-View-Controller
Wikipedia : Model-View-Controller