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
기본적으로 아래의 구성요소들로 구성
- 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()
}
(참고)