본문 바로가기

Xcode 개발

[Xcode] MVVM 패턴

MVVM 패턴의 큰 장점은
Controller의 역할 비중을 줄일 수 있다는 것이다.


가령, 기존의 MVC 패턴에서는
UI나 비지니스 로직 관련 모든 메서드를
Controller에서 구현했다.

따라서, 수많은 메서드를 구분하는 작업이
번잡하고 비효율적이었는데
MVVM 패턴을 통해 해결해보자.

 

 

간단하게, 숫자를 입력하면 세자리 수 단위를 나누는 콤마가 자동으로 찍히는 기능을 MVVM으로 구현해보자.

Observable, ViewModel, ViewController 클래스 코드로 이루어진다.

기존의 MVC 패턴을 상상하고 비교하면서 살펴보자.

 

Observable 클래스

"너희 둘, Controller랑 ViewModel끼리 직접 소통도 가능하겠지만, 내가 더 매끄럽게 도와줄께!"

"처음 Binding할 때랑 변수 값이 바뀔 때, Controller가 알려준 매서드를 실행할께!"

 

- MVVM 패턴은 Model, View, ViewModel로 이루어져있다.

- 기존 Controller는 오로지 View의 UI에 데이터를 연결해주는 최소한의 역할을 한다.

- 그리고 각종 비지니스 로직은 ViewModel이 관리한다.

- 이 때, Controller와 ViewModel 간 소통 창구가 필요한데, 이를 Observable이 해줄 수 있다.

 

(지금은 아래 코드를 훑어만 보고, 게시글을 읽어가며 필요할 때만 참고해보자)

class CObservable<T> {

    private var listener: ((T) -> Void)?
    
    var value: T {
        didSet {
            listener?(value)
        }  
    }
    
    init(_ value: T) {
        self.value = value
    }
    
    func bind(_ closure: @escaping (T) -> Void) {
        closure(value)
        listener = closure
    }
}

 

 

ViewModel 클래스

"Controller야, 이제 복잡한 로직들은 나에게 맡겨!"

"Observer야, 우선 변수 좀 저장해주고, 내가 변수 값을 바꾸면 자동으로 didSet 함수 좀 실행해주라!"

 

- 실 데이터로 쓰일 변수와 비지니스 로직을 ViewModel에서 구현한다.

- 각 변수들은 변형될 때마다 반응할 수 있도록 Observable 타입에 제너럴 매개변수 값을 넣어 초기화한다.

- ViewModel에서는 UIKit을 다루지 않기 때문에 import 하지 않는다.

 

class NumberViewModel {
    
    var pageNumber: CObservable<String> = CObservable("3000")
    
    func changePageNumberFormat(text: String) {
        
        // 숫자 세자리마다 쉼표 표시
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        
        // 쉼표가 찍힌 숫자 스트링 값을 인트 값으로 바꾸는 과정
        let text = text.replacingOccurrences(of: ",", with: "")
        guard let number = Int(text) else { return }
        
        let result = numberFormatter.string(for: number)!
        pageNumber.value = result
    }
}

 

 

ViewController 클래스

"ViewModel아, 값이 바뀔 때마다 실행될 클로져를 Observer한테 전달해줘!"

"Observer야, 우선 변수 좀 저장해주고, 내가 변수 값을 바꾸면 자동으로 didSet 함수 좀 실행해주라!"

 

- Data Binding 과정을 통해 viewDidLoad 시점에서 변수를 초기화하는 클로져를 작성한다.

- 그리고 이 클로져는 Observer 클래스를 통해 변수값이 바뀔 때마다 didSet을 통해 실행된다.

- 필요할 때마다 ViewModel 클래스로부터 비지니스 로직과 관련된 매서드들을 호출한다.

 

class NumberViewController: UIViewController {

    @IBOutlet weak var numberTextField: UITextField!
    
    var viewModel = NumberViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        bindData()
    }

    func bindData() {
        // numberTextField.text = "3,000"
        viewModel.pageNumber.bind { value in
            self.numberTextField.text = value
        }
    }
    
    func configureViews() {
        numberTextField.addTarget(self, action: #selector(numberTextFieldChange), for: .editingChanged)
    }
    
    @objc
    func numberTextFieldChange() {
        guard let text = numberTextField.text else { return }
        viewModel.changePageNumberFormat(text: text)
    }
}