🍎 iOS/Architecture

[Design Pattern] MVVM(Model - View - ViewModel) νŒ¨ν„΄μ΄λž€? (feat. Swift)

Fomagran πŸ’» 2021. 10. 19. 16:11
728x90
λ°˜μ‘ν˜•

μ•ˆλ…•ν•˜μ„Έμš” Foma πŸ’» μž…λ‹ˆλ‹€!

 

μ˜€λŠ˜μ€ 정말 μ˜€λžœλ§Œμ— λ””μžμΈ νŒ¨ν„΄μ„ μ •λ¦¬ν•˜κ²Œ λ˜μ—ˆλŠ”λ°μš”.

 

κ·Έ μ€‘μ—μ„œ κ°€μž₯ 인기(?)있고 ν•«ν•œ MVVM λ””μžμΈ νŒ¨ν„΄μ„ 닀뀄보렀고 ν•©λ‹ˆλ‹€.

 

(SwiftUI λ˜ν•œ κΈ°λ³Έ λ””μžμΈ νŒ¨ν„΄μœΌλ‘œ MVVM을 μ‚¬μš©ν•©λ‹ˆλ‹€.)

 

λ°”λ‘œ μ‹œμž‘ν• κ²Œμš”~


MVVM(Model - View - ViewModel)νŒ¨ν„΄μ΄λž€? 🧐

 

MVVM은 ν•˜λ‚˜μ˜ μ†Œν”„νŠΈμ›¨μ–΄ μ•„ν‚€ν…μ²˜ νŒ¨ν„΄ 으둜 GUI μ½”λ“œλ‘œ κ΅¬ν˜„ν•˜λŠ” κ·Έλž˜ν”½ μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€(λ·°)의 κ°œλ°œμ„ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 λ˜λŠ” λ°±μ—”λ“œ 둜직(λͺ¨λΈ)λ‘œλΆ€ν„° λΆ„λ¦¬μ‹œμΌœμ„œ λ·°κ°€ μ–΄λŠ νŠΉμ •ν•œ λͺ¨λΈ ν”Œλž«νΌμ— μ’…μ†λ˜μ§€ μ•Šλ„λ‘ ν•΄μ€€λ‹€. MVVM의 λ·° λͺ¨λΈμ€ κ°’ λ³€ν™˜κΈ°μΈλ°, μ΄λŠ” λ·° λͺ¨λΈμ΄ λͺ¨λΈμ— μžˆλŠ” λ°μ΄ν„° 객체λ₯Ό λ…ΈμΆœ(λ³€ν™˜)ν•˜λŠ” μ±…μž„μ„ 지기 λ•Œλ¬Έμ— 객체λ₯Ό κ΄€λ¦¬ν•˜κ³  ν‘œν˜„ν•˜κΈ°κ°€ μ‰¬μ›Œμ§„λ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€. μ΄λŸ¬ν•œ μ μ—μ„œ, λ·° λͺ¨λΈμ€ λ·° λ³΄λ‹€λŠ” 더 λͺ¨λΈμΈ 것이며, λͺ¨λ“  λ·°λ“€μ˜ λ””μŠ€ν”Œλ ˆμ΄ λ‘œμ§μ„ μ œμ™Έν•œ λŒ€λΆ€λΆ„μ˜ 것듀을 μ²˜λ¦¬ν•œλ‹€. - μœ„ν‚€ λ°±κ³Ό -

 

즉, 화면을 λ§Œλ“œλŠ” μ½”λ“œμ™€ 뒀에 데이터λ₯Ό μ²˜λ¦¬ν•˜λŠ” μ½”λ“œλ₯Ό λΆ„λ¦¬ν•΄μ„œ ν•˜λŠ” 것이 λ°”λ‘œ MVVM이 핡심이죠!

 

λ§ˆμ΄ν¬λ‘œμ†Œν”„νŠΈμ˜ μΌ„ 쿠퍼와 ν…Œλ“œ ν”Όν„°μŠ€μ— μ˜ν•΄ 발λͺ…λ˜μ—ˆμœΌλ©° μ‘΄ ꡬ슀먼이 2005λ…„ 처음으둜 λΈ”λ‘œκ·Έμ— λ°œν‘œν–ˆλ‹€κ³  ν•˜λ„€μš”!

 

(κ·Έλž˜λ„ μ΅œκ·Όμ— λ§Œλ“€μ–΄μ‘Œλ‹€κ³  μƒκ°ν–ˆλŠ”λ° 16λ…„μ΄λ‚˜ λ˜μ—ˆλ„€μš”..)

 

각 역할을 그림으둜 μ‚΄νŽ΄λ³΄λ©΄ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

 

 

 

 

νŠΉμ§•

 

데이터 바인딩을 μ‚¬μš©ν•˜μ—¬ λ·°κ°€ λ·°λͺ¨λΈμ˜ 값을 κ΄€μ°°ν•˜μ—¬ λ³€ν™”λ₯Ό λ°˜μ˜ν•©λ‹ˆλ‹€.


MVVM의 μž₯단점 πŸ‘πŸ»

 

μž₯점

 

  • λ·° 둜직과 λΉ„μ§€λ‹ˆμŠ€ λ‘œμ§μ„ λΆ„λ¦¬ν•˜μ—¬ 생산성을 λ†’νž 수 μžˆλ‹€. (UIκ°€ λ‚˜μ˜€μ§€ μ•Šμ•„λ„ 개발 κ°€λŠ₯)
  • ν…ŒμŠ€νŠΈκ°€ μˆ˜μ›”ν•΄μ§„λ‹€. (μ˜μ‘΄μ„±μ΄ μ—†κΈ° λ•Œλ¬Έ)
  • 뷰와 λ·°λͺ¨λΈμ΄ 1:n 관계이기 λ•Œλ¬Έμ— μ€‘λ³΅λ˜λŠ” λ‘œμ§μ„ λͺ¨λ“ˆν™” ν•΄μ„œ μ—¬λŸ¬ 뷰에 μ μš©ν•  수 μžˆλ‹€. (μ½”λ“œ μž¬μ‚¬μš© κ°€λŠ₯)
  • λ§Žμ€ 기업듀이 μ• μš©ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄μ΄λ‹€.

 

단점

 

  • μ„€κ³„ν•˜κΈ°κ°€ λ³΅μž‘ν•˜λ‹€. (Rx,데이터 바인딩에 λŒ€ν•œ 지식 ν•„μš”)
  • λ·°λͺ¨λΈμ΄ λΉ„λŒ€ν•΄μ§ˆ 수 μžˆλ‹€.
  • 데이터 λ°”μΈλ”©μœΌλ‘œ μΈν•œ λ©”λͺ¨λ¦¬ μ†Œλͺ¨κ°€ μ‹¬ν•˜λ‹€.

μ½”λ“œ κ΅¬ν˜„

 

μ˜€λŠ˜μ€ κ°„λ‹¨ν•˜κ²Œ Rxλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  MVVM을 κ΅¬ν˜„ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

이전에 MVC,MVPλ₯Ό κ΅¬ν˜„ν–ˆλ˜ 캐릭터 화면을 κ·ΈλŒ€λ‘œ μ“°κ² μŠ΅λ‹ˆλ‹€.

 

(κ°„λ‹¨ν•˜κ²Œ 과정을 μ„€λͺ…λ“œλ¦¬λ©΄ μ•„λž˜ Previous와 Next λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ 캐릭터 λͺ¨λΈμ΄ 이전 λ˜λŠ” λ‹€μŒ λͺ¨λΈλ‘œ λ°”λ€Œκ²Œ 되고

 

λ·°λŠ” 바뀐 λͺ¨λΈμ— 따라 λ„μš°λŠ” 정보가 λ°”λ€Œκ²Œ λ©λ‹ˆλ‹€.)

 


Observable

 

MVVM의 핡심인 μ˜΅μ €λ²„λΈ”μ„ κ΅¬ν˜„ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

λ¨Όμ € Listenerλ₯Ό 읡λͺ… ν΄λ‘œμ €λ‘œ λ§Œλ“€μ–΄λ†“μŠ΅λ‹ˆλ‹€.

 

valueλŠ” didSet을 μ΄μš©ν•΄μ„œ λ¦¬μŠ€λ„ˆμ˜ μ–΄λ–€ 값이 λ“€μ–΄μ˜€λ©΄ 그에 맞게 λ³€ν™”ν•˜λ„λ‘ λ§Œλ“€μ–΄λ†“μŠ΅λ‹ˆλ‹€.

 

bind λ©”μ„œλ“œλ₯Ό ν†΅ν•΄μ„œ ν˜„μž¬ Observable의 λ¦¬μŠ€λ„ˆλ₯Ό μ μš©ν•˜κ³  λ¦¬μŠ€λ„ˆλŠ” 값이 λ³€κ²½λ˜λ©΄ λ°”λ‘œ 클둜져λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.

 

import Foundation

class Observable<T> {
  typealias Listener = (T) -> Void
  var listener: Listener?
  var value: T {
    didSet {
      listener?(value)
    }
  }
  init(_ value: T) {
    self.value = value
  }
  func bind(listener: Listener?) {
    self.listener = listener
    listener?(value)
  }
}

Model

 

캐릭터λ₯Ό κ΅¬μ„±ν•˜λŠ” λͺ¨λΈμž…λ‹ˆλ‹€.

 

import UIKit

struct MVVM_Character {
    
    //화면을 ꡬ성할 데이터
    let image:UIImage
    let name:String
    let gender:String
    let country:String
    
    //μ΄ˆκΈ°ν™”
    init(name:String,image:UIImage,gender:String,country:String) {
        self.name = name
        self.image = image
        self.gender = gender
        self.country = country
    }
}

View

 

λ·°λŠ” μ•„λž˜μ™€ 같이 λ·°λͺ¨λΈμ„ μ†Œμœ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

λ·°λͺ¨λΈμ˜ ν”„λ‘œνΌν‹°λ“€μ„ 뷰의 ν”„λ‘œνΌν‹°μ™€ 바인딩을 μ‹œμΌœμ€λ‹ˆλ‹€. (이것이 λ°”λ‘œ 데이터 λ°”μΈλ”©μž…λ‹ˆλ‹€.)

 

즉, λ·°λͺ¨λΈμ„ κ΄€μ°°ν•˜κ³  λ°”λ€ŒλŠ” 값에 λ”°λΌμ„œ λ·°λ₯Ό λ³€ν™”μ‹œμΌœμ€€λ‹€κ³  μ„€μ •ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

 

μœ μ €μ˜ μΈν„°λž™μ…˜μ„ λ°›κ²Œ 되면 λ·°λͺ¨λΈμ˜ νŠΉμ • λ©”μ„œλ“œλ₯Ό μ‹€ν–‰μ‹œμΌœ λ·°λͺ¨λΈ 값을 λ°”κΏ”μ€λ‹ˆλ‹€.

 

ν•œλ§ˆλ””λ‘œ λ·°μ—μ„œ μ–΄λ–€ λ²„νŠΌμ΄ 눌리면 -> λ·°λͺ¨λΈμ˜ 값이 λ³€ν™”ν•˜κ³  -> 값이 λ³€ν™”ν•˜λ©΄ λ·°κ°€ 값에 맞게 λ³€ν™”ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

 

μ΄λ ‡κ²Œ 보면 뷰의 μ½”λ“œκ°€ MVC에 λΉ„ν•΄ 훨씬 간결해진 것을 λ³Ό 수 있죠?

 

import UIKit

class MVVM_CharacterViewController: UIViewController {
    
    //MARK:- IBOutlets
    
    @IBOutlet weak var country: UILabel!
    @IBOutlet weak var name: UILabel!
    @IBOutlet weak var gender: UILabel!
    @IBOutlet weak var image: UIImageView!
    @IBOutlet weak var nextButton: UIButton!
    @IBOutlet weak var previousButton: UIButton!
    
    //MARK:- Properties
    
    private let viewModel:CharacterViewModel = CharacterViewModel()
    
    //MARK:- Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
        bind()
    }
    
    //MARK:- Functions
    
    func bind() {
        viewModel.image.bind { [weak self] image in
            self?.image.image = image
        }
        
        viewModel.name.bind { [weak self] name in
            self?.name.text = name
        }
        
        viewModel.gender.bind { [weak self] gender in
            self?.gender.text = gender
        }
        
        viewModel.country.bind { [weak self] country in
            self?.country.text = country
        }
    }
    
    //MARK:- IBActions
    
    @IBAction func tapPreviousButton(_ sender: Any) {
        viewModel.tapButton(isPrevious: true)
    }
    
    @IBAction func tapNextButton(_ sender: Any) {
        viewModel.tapButton(isPrevious: false)
    }
}

ViewModel

 

μš°μ„  전체 λͺ¨λΈ λ°μ΄ν„°λŠ” μž„μ˜λ‘œ 미리 λ§Œλ“€μ–΄λ†“μ•˜μŠ΅λ‹ˆλ‹€.

 

뷰에 ν•„μš”ν•œ ν”„λ‘œνΌν‹°λ“€μ„ Observable νƒ€μž…μœΌλ‘œ λ§Œλ“€μ–΄λ†“μŠ΅λ‹ˆλ‹€.

 

λ·°μ—μ„œ μΌμ–΄λ‚˜λŠ” μΈν„°λž™μ…˜μ— 따라 값이 λ³€ν™”ν•˜λ„λ‘ ν•˜λŠ” λΉ„μ§€λ‹ˆμŠ€ λ‘œμ§μ„ μž‘μ„±ν•©λ‹ˆλ‹€.

 

import UIKit

class CharacterViewModel {
    
    //MARK:- Properties
    
    let image:Observable<UIImage?> = Observable(nil)
    let name:Observable<String?> = Observable(nil)
    let gender:Observable<String?> = Observable(nil)
    let country:Observable<String?> = Observable(nil)
    var index:Int = 0
    
    init() {
        self.image.value = characters[0].image
        self.name.value = characters[0].name
        self.gender.value = characters[0].gender
        self.country.value = characters[0].country
    }
    
    func tapButton(isPrevious:Bool) {
        if isPrevious {
            index = index > 0 ? index-1 : 0
        }else {
            index = index < characters.count - 1 ? index + 1 : characters.count - 1
        }
        self.image.value = characters[index].image
        self.name.value = characters[index].name
        self.gender.value = characters[index].gender
        self.country.value = characters[index].country
    }
}

μ‹€ν–‰ν™”λ©΄ 

 


λŠλ‚€μ 

 

μ§€κΈˆκΉŒμ§€ MVVM νŒ¨ν„΄μ„ μ‚¬μš©ν•΄μ„œ 앱을 κ΅¬ν˜„ν•˜λ©΄μ„œ μƒκ°ν•˜λŠ” 방식이 λ³€ν™”ν•˜λŠ” 것을 λŠκΌˆλ‹€.

 

μ›λž˜ MVC둜 κ΅¬ν˜„ν•œλ‹€λ©΄ "λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ λ·°κ°€ μ΄λ ‡κ²Œ λ³€ν™”ν•˜λ©΄ λ˜κ² λ‹€"라면

 

MVVMμœΌλ‘œλŠ” "λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ λ·°λͺ¨λΈμ—κ²Œ μ•Œλ €μ£Όκ³  λ·°λͺ¨λΈμ˜ κ°’λŒ€λ‘œ λ·°κ°€ λ³€ν•˜κ² λ‹€."둜 λ°”λ€Œμ—ˆλ‹€.

 

λ‘˜μ˜ 차이점은 MVCλŠ” λ·°λ₯Ό λ‚΄κ°€ 직접 λΉ λ₯΄κ²Œ λ°”κΎΈλŠ” λŠλ‚Œμ΄κ³  MVVM은 미리 μž‘μ„±ν•΄λ†“μ€ λ°©μ‹λŒ€λ‘œ λ·°κ°€ μ•Œμ•„μ„œ λ°”λ€ŒλŠ” λŠλ‚Œμ΄μ—ˆλ‹€.

 

ν•˜μ§€λ§Œ ν™•μ‹€νžˆ μž₯단점은 μžˆμ—ˆλ‹€.

 

MVVM은 μ‹€μ œλ‘œ λΉ„μ§€λ‹ˆμŠ€ 둜직이 λ·°λͺ¨λΈμ— μžˆμ–΄μ„œ μ–΄λ–€ μ½”λ“œλ₯Ό μ°Ύκ±°λ‚˜ 읽을 λ•Œ 훨씬 μˆ˜μ›”ν–ˆλ‹€.

 

λ˜ν•œ 미리 μž‘μ„±ν•΄λ†“μ€ μ½”λ“œλ₯Ό μž¬μ‚¬μš©ν•  μˆ˜λ„ μžˆμ–΄μ„œ 생산적인 νš¨μœ¨μ„ μ§μ ‘μ μœΌλ‘œ 많이 λŠκΌˆλ‹€.

 

ν•˜μ§€λ§Œ κ°„λ‹¨ν•œ 화면을 λ§Œλ“€ λ•Œμ‘°μ°¨ 머리가 λ³΅μž‘ν•΄μ§€κ³  μ‹œκ°„λ„ 였래 κ±Έλ Έλ‹€...

 

MVVM νŒ¨ν„΄μ„ μ μš©ν–ˆμœΌλ©΄ 아무리 κ°„λ‹¨ν•œ 화면이라도 뭐든 ν•˜λ‚˜λΆ€ν„° μ—΄κΉŒμ§€ MVVM으둜 κ΅¬ν˜„ν•΄μ•ΌλΌ!

 

라고 μƒκ°ν•˜κ³  μ‹œκ°„μ„ 많이 썼던 것 κ°™λ‹€.

 

ν•˜μ§€λ§Œ κ°€μž₯ 쒋은건 MVC에 μ ν•©ν•œ 화면은 MVCλŒ€λ‘œ MVVM에 λ§žλŠ” ν™”λ©΄ MVVMλŒ€λ‘œ μ ν•©ν•œ λ””μžμΈ νŒ¨ν„΄μ„ μ‚¬μš©ν•΄κ°€λ©΄μ„œ

 

μƒμ‚°μ„±μ΄λ‚˜ ν˜‘μ—…μ˜ νš¨μœ¨μ„ μ˜¬λ €μ£ΌλŠ” λ””μžμΈ νŒ¨ν„΄μ„ μ μš©ν•΄μ„œ κ°œλ°œν•˜λŠ” 것이 μ’‹λ‹€κ³  λŠκΌˆλ‹€.

 


Source Code

 

각 λ””μžμΈ νŒ¨ν„΄μ˜ μ†ŒμŠ€μ½”λ“œλŠ” μ•„λž˜ 깃헙에 μ •λ¦¬ν•΄λ†“κ² μŠ΅λ‹ˆλ‹€!

 

 

GitHub - fomagran/Architecture-Example: iOS 아킀텍쳐 예제 λͺ¨μŒ 🏠

iOS 아킀텍쳐 예제 λͺ¨μŒ 🏠. Contribute to fomagran/Architecture-Example development by creating an account on GitHub.

github.com


Reference

μ•„λž˜ μ‚¬μ΄νŠΈλ₯Ό μ°Έκ³ ν–ˆμŠ΅λ‹ˆλ‹€.

 

 

Design Patterns by Tutorials: MVVM

Learn how and when to use the architecture-slash-design pattern of MVVM in this free chapter from our new book, Design Patterns by Tutorials!

www.raywenderlich.com

 

728x90
λ°˜μ‘ν˜•