apple/DesignPattern, Architecture

Swift 디자인패턴 MVVM Pattern (MVVM 패턴)

lgvv 2022. 4. 26. 14:15

Swift 디자인패턴 MVVM Pattern (MVVM 패턴)

 

MVVM 패턴은 모바일 개발에서 자주 사용하는 아키텍처 패턴.

 

히스토리

  • 2022-04-26: 디자인 패턴 스터디 정리
  • 2024-11-28: 포스팅 글 재정리 및 예제 변경

 

MVVM Pattern

 

 

MVVM Pattern

기본적으로 아래의 구성요소들로 구성

  • Model: 앱의 데이터 및 비즈니스 로직의 일부
  • View: 앱의 화면 영역 담당
  • ViewModel: View와 Model의 중재자 역할

MVVM 패턴은 파생 아키텍처가 많고, 아키텍처에 따라 용어의 차이도 존재하여, 가장 기본적인 것들을 서술

  • 클린아키텍처
  • MVVM-C
  • VIPER
  • RIBs
  • ReactorKit
  • TCA

 

코드 예제


UIKit이랑 SwiftUI에서 모두 MVVM을 사용하는 기본 예제를 확인할 수 있도록 만들어 둠

  • ViewModel은 해당 형태로 재활용 가능
  • 이벤트 전달을 위한 클로저 혹은 Delegate 대신 @Published를 사용
    • 예전에 개발할 때는 이런게 없어서 클로저 혹은 RxSwift를 활용해서 구현
  • 액션을 분리하는 이유는 ViewModel의 메서드를 직접 호출할 경우 사실상 절차지향과 다를 바 없어짐
    • 객체지향 관점에서 커맨드를 반드시 분리해야 함.
    • MVC에서 ViewModel을 만들어 상태와 메서드만을 관리한다고 해서 MVVM이 아님
    • 명령과 메서드를 분리하는 개념인데, 명령이란 호출을 하는거고, 메서드는 상태를 바꾸기도 함.

 

import UIKit
import SwiftUI
import Combine

struct Model {
    var name: String
    var count: Int
}

class ViewModel: ObservableObject {
    @Published var model: Model = .init(name: "김철수", count: 0)
    
    enum Action {
        case up
        case down
    }
    
    func send(action: Action) {
        switch action {
        case .up:
            model.count += 1
        case .down:
            model.count -= 1
        }
    }
}

struct SwiftUIView: View {
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        VStack(spacing: 10) {
            Button("Count Up") {
                viewModel.send(action: .up)
            }
            Button("Count Down") {
                viewModel.send(action: .down)
            }
            Text(viewModel.model.name + "의 숫자는" + "\(viewModel.model.count)")
        }
    }
}

class ViewController: UIViewController {
    private let viewModel: ViewModel
    private var cancellables: Set<AnyCancellable> = []
    
    private func bind() {
        viewModel.$model
            .sink { [weak self] model in
                guard let self else { return }
                self.label.text = model.name + "의 숫자는" + "\(model.count)"
            }.store(in: &cancellables)
    }
    
    init(viewModel: ViewModel) {
        self.viewModel = viewModel
        self.cancellables = .init()
        
        super.init(nibName: nil, bundle: nil)
        
        bind()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - UIComponents
    
    private lazy var upButton: UIButton = {
        let btn = UIButton()
        btn.addTarget(self, action: #selector(upButtonTapped), for: .touchUpInside)
        return btn
    }()
    
    @objc
    private func upButtonTapped() {
        viewModel.send(action: .up)
    }
    
    private lazy var downButton: UIButton = {
        let btn = UIButton()
        btn.addTarget(self, action: #selector(upButtonTapped), for: .touchUpInside)
        return btn
    }()
    
    @objc
    private func downButtonTapped() {
        viewModel.send(action: .down)
    }
    
    private let label = UILabel()
}

 

 

 

(참고)

 

 

Design Patterns by Tutorials, Chapter 10: Model-View-ViewModel Pattern

Use this pattern when you need to transform models into another representation for a view. This pattern compliments MVC especially well. You’ll embark on a new project — CoffeeQuest — to help you find the best coffee shops around.

www.raywenderlich.com