itisjustK
코딩과 사람 사는 이야기
itisjustK
전체 방문자
오늘
어제
  • 분류 전체보기 (207)
    • 일이삼사오육칠팔구십일이삼사오육칠팔구십일이삼사오육칠.. (0)
    • Web (43)
      • html & css (9)
      • django & python (15)
      • java script (9)
    • iOS (51)
      • Swift (42)
      • SwiftUI (5)
    • CS (25)
      • 자료구조 (6)
      • 운영체제 (3)
      • 데이터베이스 (9)
      • 네트워크 (7)
    • PS (34)
      • 알고리즘 & 자료구조 (0)
    • Life (36)
    • Retrospective (15)
    • Book (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • CS
  • 개발자
  • SwiftUI
  • 독립서점
  • 생활코딩
  • 생활코딩 #이고잉 #HTML #코딩 #개발자
  • 어플
  • CoreData
  • 킨디
  • AppleDevloperAcademy
  • 점주
  • binding
  • ios
  • mongodb
  • nosql
  • POSTECH
  • 세그멘테이션
  • SWIFT
  • crud
  • 연결리스트

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
itisjustK

코딩과 사람 사는 이야기

[iOS] MVVM 패턴이란?
iOS

[iOS] MVVM 패턴이란?

2022. 10. 12. 20:21

✨ MVVM 패턴 탄생 배경

  1. MVC가 간단한 프로젝트에는 용이하지만, 프로젝트가 거대해지고 개발자간의 협업이 늘어날수록 Controller의 규모도 거대해진다는 단점이 존재했다.
    • Controller의 역할 : 레이아웃 코드들, 유저 입력 프로세싱, 비지니스 로직, 데이터 변환, 화면 전환, 생명주기, 콜백처리(델리게이트 등), 네트워크 통신 등.
  2. Controller의 역할을 덜어주기 위해 새로운 디자인 패턴 MVP (Model View Presenter)를 고안해냈다.
    • ViewController를 View로 취급, Model은 Model, 중개자 역할로 Presenter 추가.
  3. 하지만 Presenter와 View가 서로를 소유하여 높은 의존성을 띄고 있었고 View가 하는 일이 거의 없었다. -> 개선 여지 O
    • Presenter가 weak로 View를 소유하고, View에 있는 요소에 직접 접근하여 업데이트한다.
    • VC는 꼭 VC에서 처리해야하는 것들 ( 생명주기, 화면 전환, 콜백처리 등 )만 처리하고, 나머지는 모두 Presenter로 위임하였다.
  4. 이렇게 MVVM 디자인 패턴이 탄생했다. (Model, View, ViewModel)
    • View를 업데이트하는 코드들은 View에 두고, 이 코드들을 트리거하기 위해 Data Binding으로 ViewModel과 연결해준다.
    • 그 결과, 뷰 로직과 비즈니스 로직이 분리되었다.
    • Binding으로 VM과 V가 데이터를 공유하고, V에서는 뷰 로직을, VM에서는 비즈니스 로직을 담당한다.

 

 

✨ MVVM 요소

  • Model
    • 데이터, 네트워크 로직, 비즈니스 로직, 데이터 캡슐화
    • View, ViewModel과 완전한 분리 -> 다른 거 신경쓰지 않고, 데이터가 어떻게 보여질지만 신경쓴다.
    • MVC의 Model과 비슷
  • View
    • VM으로부터 데이터 가져와서 View를 직접 그린다. (MVC와의 차이점) 
    • 비즈니스 로직과 UI 로직 구분할 수 있다. (View에서 UI로직을 담당하기 때문에)
    • 유저 action 수신해서 이에 대한 처리는 VM에 부탁한다. 
    • ViewModel 참조한다 (소유한다)
  • ViewModel
    • View가 요청한 사항 처리하는 로직을 담고 있다.
    • Model에 변화가 생기면 View에 notification 보낸다.
    • View ~ Model 사이의 중재자 역할이라고 보면 된다.
    • View없이 테스트 가능하다.
    • ViewModel은 변경사항을 직접 View에게 알리지 않고 발표한다. (publish) 
    • 이를 보고 있는 View가 알아차리고 View를 다시 그리는 등의 행동을 수행한다.

 

 

✨ MVVM 동작 흐름

View에 들어온 Event를 VM에 알림 -> VM이 처리 (Model에 알림) -> Model 변화 -> VM이 알아차림 -> 바인딩 되어있는 View 업데이트 

 

 

 

✨ MVC vs MVVM

 

❗️행동 결정권자 차이 (어떤 행동을 할지)

  • MVC 
    • 유저 상호작용이 발생했음을 View -> Controller에게 알리고 어떤 행동을 할지 Controller가 다 정한다. (Controller가 View를 그리는 과정이 포함된다.)
  • MVVM
    • VM은 로직만 가지고 있고, 어떤 행동을 할지는 View가 정한다. (VM을 View가 소유하면서 행동 결정한다.)

 

❗️데이터 변경 상황 차이

  • MVC
    • Model -> Controller 에게 알린다.
    • 상황에 따라 View를 어떻게 그릴지 Controller가 판단한다.
  • MVVM 
    • Model -> VM 에게 알린다. (VM이 Model의 변경 사항 알아차린다.)
    • 그러면 VM -> View 에게 알리고 (View가 VM의 변경 사항 알아차린다.)
    • 상황에 따라 View를 어떻게 그릴지 View가 판단한다. 

 

❗️기준 관점 차이

  • UIKit - MVC : event driven
    • 이벤트에 따라 특정 로직 실행하고, 이에 따라 View 변경한다.
  • SwiftUI - MVVM : data driven
    • 데이터 변경에 따라 특정 로직 실행하고, 이에 따라 View 변경한다.

 

 

✨ MVVM 예시 코드

❗️View & ViewModel

 

- LoginView (LoginViewController + LoginViewModel)

// LoginViewController.swift

// View of MVVM

import UIKit

class LoginViewController: UIViewController {
    
    @IBOutlet weak var emailField: UITextField!
    @IBOutlet weak var passwordField: UITextField!
    
    private let viewModel = LoginViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupBinders()
    }
    
    // 뉴스 채널 구독한다는 함수
    // bind 라는 게 View와 VM을 연결해주는 건데, 여기서 뭘 연결해주는데? 라고 이해함 나는.
    // 근데 이 함수를 보면, error를 가져오고, 그에 대한 로직까지 같이 담는중.
    // 아하, VM에 있는 error를 가져오는 구나
    // 가져오는 거 + 이에 대한 로직까지. (View 그리는)
    private func setupBinders() {
        viewModel.error.bind { [weak self] error in
            if let error = error {
                print(error)
            } else {
                self?.goToHome()
            }
        }
    }
    
    private func goToHome() {
        let vc = storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
        present(vc, animated: true, completion: nil)
        
    }
    
    @IBAction func loginBtnClicked(_ sender: UIButton) {
        guard let email = emailField.text, let password = passwordField.text else {
            print("please enter email and password")
            return
        }
        viewModel.login(email: email, password: password)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }
}
// LoginViewModel.swift 

// ViewModel of MVVM (LoginViewController 라는 뷰의 뷰모델)

import Foundation

final class LoginViewModel {
    
    // error라는 것을 ObservableObject 타입으로 만드는 이유
    // MVC와 달리, 로그인 실패 시 하는 행동을, 여기서 바로 View를 수정할 수 없다.
    // 그렇기 떄문에 error라는 것을 만들어서 bind할 수 있는 ObservableObject 타입으로 지정해주고 View가 이걸 보게끔 한다.
    var error: ObservableObject<String?> = ObservableObject(nil)
    
    func login(email: String, password: String) {
        NetworkService.shared.login(email: email, password: password) { [weak self] success in
            self?.error.value = success ? nil : "Invalid Credentails"
        }
    }
}

 

 

- HomeView (HomeViewController + HomeViewModel)

// HomeViewController

import UIKit

// 목표 : 로그인한 유저 정보 화면에 띄우기
// 1. 유저 정보 가져오기 (VM -> V)
class HomeViewController: UIViewController {

    @IBOutlet weak var welcomeLbl: UILabel!
    
    private var viewModel = HomeViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        viewModel.getUser()
        setupBinders()
    }
    
//    private func getUser() {
//        if let user = NetworkService.shared.user {
//            welcomeLbl.text = "Welcome, \(user.firstName) \(user.lastName)"
//        } else { return }
//    }
    
//    private func loadUser() {
//        viewModel.getUser()
//        welcomeLbl.text = viewModel.welcomeMessage.value!
//    }
    
    private func setupBinders() {
        viewModel.welcomeMessage.bind { [weak self] message in
            self?.welcomeLbl.text = message
        }
    }
}
// HomeViewModel.swift

// ViewModel of MVVM (HomeViewController 라는 뷰의 뷰모델)

import Foundation

final class HomeViewModel {
    
    var welcomeMessage: ObservableObject<String?> = ObservableObject(nil)
    
    func getUser() {
        if let user = NetworkService.shared.user {
            welcomeMessage = ObservableObject("Welcome, \(user.firstName) \(user.lastName)")
        } else { return }
    }
}

 

 

- ViewModel 보조 수단들 : NetworkService & ObservableObject 

// ObservableObject.swift

// View - ViewModel 데이터 바인딩을 가능케하는 클래스

import Foundation

class ObservableObject<T> {
    
    // 뉴스
    var value: T {
        didSet {
            listener?(value)
        }
    }
    
    private var listener: ((T) -> Void)?
    
    init(_ value: T) {
        self.value = value
    }
    
    // 다른 사람들에게 뉴스를 알리는 함수
    func bind(_ listener: @escaping (T) -> Void) {
        
        // listener에게 뉴스를 알린다 (listener들이 기다릴 필요 없이 변화 바로 알아차린다)
        listener(value)
        self.listener = listener
    }
}
// NetworkService.swift

import Foundation

final class NetworkService {
    
    static let shared = NetworkService()
    var user: User?
    
    private init() {}
    
    func login(email: String, password: String, completion: @escaping (Bool) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            [weak self] in
            if email == "test@test.com" && password == "password" {
                self?.user = User(firstName: "K", lastName: "Park", email: "test@test.com", age: 27)
                completion(true)
            } else {
                self?.user = nil
                completion(false )
            }
        }
    }
    
    func goToHome() {
        
    }
}

 

 

❗️Model

// User.swift

// Model of MVVM

import Foundation

struct User {
    let firstName, lastName, email : String
    let age: Int
}

 

 

 

✨ MVVM 예시 코드 결과

 

 

 

출처

  • https://eeyatho.tistory.com/77
  • https://velog.io/@ictechgy/MVVM-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4
저작자표시 (새창열림)

'iOS' 카테고리의 다른 글

[iOS] 의존성 주입(Dependency Injection, DI)이 객체의 결합도를 낮춰주는 이유 (Feat. Car)  (0) 2023.03.08
[iOS] MVC 패턴이란?  (0) 2022.10.09
iOS App States  (0) 2022.04.29
    'iOS' 카테고리의 다른 글
    • [iOS] 의존성 주입(Dependency Injection, DI)이 객체의 결합도를 낮춰주는 이유 (Feat. Car)
    • [iOS] MVC 패턴이란?
    • iOS App States
    itisjustK
    itisjustK

    티스토리툴바