Architecture_MVC, MVP, MVVM

JinSeok Hong·2021년 7월 6일
0

Architecture Pattern

이번 시간에는 여러가지 아키텍처 패턴들에 대해 공부할 계획이다. 나는 지금까지 iOS 개발에서 MVC 패턴만을 이용해왔다. 하지만 최근 현업에서 RxSwift를 이용한 MVVM 패턴을 요구하는 것을 자주 확인할 수 있었고 여러 아키텍처와 관심을 갖게되었다.

그 중에서 대표적으로 사용되는 패턴인 MVC, MVP, MVVM에 대해 정리를 하려한다. (VIPER는 나중에 추가적으로 공부할 것이다.)

참고자료 : https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52#.wtcp3gqzw


어떠한 아키텍처를 쓸지 고르는 것이 왜 중요한가?

만약 아키텍처에 대해 고려하지 않는다면, 대규모 개발을 하던 도중 디버깅에 문제가 생겼을 때 수많은 클래스들 속에서 버그를 찾고 해결하는 것은 불가능에 가까워질 것이다.

당연히 우리는 모든 세부내용까지 다 머리에 담아놓을 수 없고 이를 해결하기 위해서는 적절한 아키텍처를 적용하여 적절하게 분리시킬 필요가 있다.

이러한 분산을 통해 우리는 어떠한 기능이 어떻게 동작하는지 쉽게 파악할 수 있다. 수많은 개체들간의 책임을 나누는 가장 쉬운 방법은 단일 책임 원칙을 적용하는 것이다.

또한 쉽게 unit test를 할 수 있으며 런타임 내에서 이슈를 찾는 것을 도와준다.


좋은 아키텍처의 특징

  1. 엄격한 규칙을 통해 개체들 간의 균형적인 책임 분산 (분산)
  2. 쉽게 테스트할 수 있는 기능 (유닛테스트)
  3. 편한 사용성과 낮은 유지보수 비용 (사용성)

필수 MV(X)

이번 시간에는 MVC, MVP, MVVM에 대해 알아보기로 했다.

Models - 데이터를 조작하는 도메인 데이터, 또는 데이터 접근 계층을 책임지고 있는 부분이다.
Views - 보통 iOS에서 'UI'로 시작하는 것들 (UILabel , UIButton, UIView, ...)을 책임지고 있는 부분이다.
Controller/Presenter/ViewModel - Model과 View의 중재자, 중간다리 역할이라고 보면 된다. 보통은 Model의 data가 변경되면 그에 따라 View를 갱신시켜는 것을 책임지고 있는 부분이다.


MVC

위의 사진은 Apple에서 기대한 (예상) Cooca MVC 패턴이다. Controller은 View와 Model을 연결시켜 주는 역할을 한다. 따라서 양쪽에 대한 정보가 없어도 되고 특정한 로직을 가지고 있기만 하면 된다. 대신 그만큼 재사용을 하기가 어렵다.
하지만 이러한 이유들로 엄청나게 많은 코드들이 Controller에 모이게 되고 크기가 너무 커진다. 우스갯소리로 사람들은 MVC를 Masssive View Controller라고도 하며 뷰 컨트롤러 offloading(덜어내기)는 iOS 개발자 사이에서 중요한 주제이다.
결국 예상과 달리 실제 로직은 아래와 같이 작동한다. 거대하면서도 view와 controller가 뒤엉켜버린 덩어리..

tableview, colloectionview를 짜며 아래와 같은 셀 재사용을 위한 코드를 자주 구현했을 것이다. 사실 여기서 이용되는 셀도 model과 view가 직접적으로 연결되기에 MVC 가이드라인을 위반한 것이다. 그렇다고 또 controller를 거치기에는 덩어리가 커지고..

좋은 아키텍처의 특징과 비교하여 정리를 해보자면 우선 iOS 개발에서의 MVC는 개발 속도면에서 강점을 보인다. 로직에 대한 측면은 view controller 한 곳에서 view와 밀접하게 개발하면 되고, 스토리보드에서 레이아웃을 짜고 정말 편하기에 사용성을 만족한다.

다만 분리성 면에서 view와 controller가 붙어있는 것을 해결하지 못해 덩어리 형태를 갖고 이러한 이유로 유닛테스트를 하기에 다른 아키텍처에 비해 적절하지 못하다.

//MVC 예시
import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

class GreetingViewController : UIViewController { // View + Controller
    var person: Person!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.greetingLabel.text = greeting
        
    }
    // layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;

MVP

언뜻 보기에는 MVC와 매우 유사하지만 중간 매개체 역할을 하는 Presenter가 존재한다. 이것은 뷰 컨트롤러의 life cycle에 어떠한 영향도 끼치지 않으며 view가 쉽게 테스트 되도록 복사본(moked)을 만드는 것이 가능하다.

따라서 Presenter에는 layout 관련 코드가 없으며 데이터와 상태에 따라 view를 업데이트하는 책임만을 갖는다.
MV(X) 패턴에서는 model과 view과 서로에 대해 아는 것을 원치 않는다(분리). 따라서 View Controller를 덩어리 만들지 않고 Presenter에서 작동하도록 하는 것이다.

좋은 아키텍처의 특징과 비교하여 정리를 해보자면 우선 분리성 면에서는 좋다. Presenter와 Model은 거의 분리했고 View는 텅 빈 느낌이다. 두번째로 유닛테스트 면에서는 이러한 텅빈 view로 인해 대부분의 비지니스 로직을 테스트하기에 매우 좋다.
다만 긴 코드로 인해 사용성 면에서는 좋지 않다.

//MVC 예시
import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}

class GreetingPresenter : GreetingViewPresenter {
    unowned let view: GreetingView
    let person: Person
    required init(view: GreetingView, person: Person) {
        self.view = view
        self.person = person
    }
    func showGreeting() {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var presenter: GreetingViewPresenter!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        self.presenter.showGreeting()
    }
    
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    
    // layout code goes here
}
// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

MVVM

MV(X) 종류 중 가장 최근에 나온 것이다. 이론적으로 Model-View-ViewModel로 나뉘며 ViewModel이 중간 매개체 역할을 한다.
MVP와 매우 유사하기도 한데, iOS 개발에 사용하는 view controller는 View라고 취급하며 view와 viewmodel의 관계가 바인딩되어있다.
바인딩은 이전에 공부한 RxSwift에서 다룬 subscribe라 이해하면 좋을거같다.

'ViewModel은 Model에서 변경을 호출하고 업데이트된 Model로써 자신을 업데이트한다'라는 것이 제일 핵심이라고 생각한다.

좋은 아키텍처의 특징과 비교했을 때 MVP와 같이 model, view를 분리하고 viewmodel이 중간 매개체 역할을 하며 바인딩 되어있는 형태이기에 분산처리가 잘되어있다. 두번째로 viewmodel은 view에 대해 전혀 모르기에 쉽게 테스트할 수 있다. 세번째로 바인딩을 사용한다면 뷰를 갱신하는 코드도 추가적으로 필요하지 않기에 더 적은 코드를 만들어낼 수 있다.

//예시

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
    init(person: Person)
    func showGreeting()
}

class GreetingViewModel : GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet {
            self.greetingDidChange?(self)
        }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) {
        self.person = person
    }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}

class GreetingViewController : UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] viewModel in
                self.greetingLabel.text = viewModel.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

0개의 댓글