MVVM

윤주현·2023년 7월 27일

Design Pattern

목록 보기
2/4

Model-View-ViewModel

역할

  • Model : MVC 패턴과 마찬가지로 앱의 데이터를 담는다.(주로 구조체)
  • View : UI를 화면에 표시한다.
  • View Model : Model이 가지고 있는 데이터를 View에 표시할 수 있는 데이터로 변환한다.(주로 클래스)

특징

  1. 뷰 컨트롤러의 역할이 너무 많은 MVC 패턴의 단점을 보완한다.
  2. 유닛 테스트 코드를 작성하기에 좋다.
  3. 뷰에 들어가는 데이터를 추가하거나 변경하기 수월하다.
  4. 뷰 모델과 뷰는 1:n의 관계를 갖는다.
  5. 뷰는 뷰 모델을 바라만 보고 뷰 모델은 뷰를 변경하는 것을 지시하지 않는다. 뷰 모델의 값이 업데이트되면 자동으로 뷰에 적용된다.(Binding)

Binding

View To View Model

struct LoginViewModel {
    var username: String = ""
    var password: String = ""
}

class BindingTextField: UITextField {
    
    var textChanged: (String) -> Void = { _ in }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        addTarget(self, action: #selector(textFieldDidChanged), for: .editingChanged)
    }
    
    func bind(callback: @escaping (String) -> Void) {
        textChanged = callback
    }
    
    @objc func textFieldDidChanged(_ textField: UITextField) {
        if let text = textField.text {
            textChanged(text)
        }
    }
}

let usernameTextField = BindingTextField()
usernameTextField.placeholder = "Enter username"
usernameTextField.backgroundColor = UIColor.lightGray
usernameTextField.borderStyle = .roundedRect
usernameTextField.bind { [weak self] text in
    self?.loginVM.username = text
}

View Model To View

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

struct LoginViewModel {
    var username = Dynamic("")
    var password = Dynamic("") 
}

override func viewDidLoad() {
    super.viewDidLoad()

    loginVM.username.bind { [weak self] text in
        self?.usernameTextField.text = text
    }

    loginVM.password.bind { [weak self] text in
        self?.passwordTextField.text = text
    }
}

Tip

  1. 뷰 모델을 하나의 뷰에만 사용한다면 configure 메소드를 뷰 모델 안에 구현하고 여러 뷰에 사용한다면 각 뷰에 구현하는 것이 좋을 수도 있다.
  2. 무조건 MVVM이 좋은 것은 아니다. 경우에 따라 MVC 패턴을 사용하는 것이 더 나은 방법일 수도 있고 MVVM과 다른 디자인 패턴을 섞어서 사용하는 방법도 있다.

참고

Design Patterns by Tutorials: MVVM

0개의 댓글