토이 프로젝트에 MVVM 패턴을 적용하려고 하는데 MVVM에서 가장 중요한 개념인 data binding을 사용하기 위해서는 Observable에 대한 이해가 필요했다.
적용하는 과정을 정리했다.
우선 내가 만들고 싶은 기능은
- 사용자가 값을 입력하면 입력 값과 저장되어있는 정보를 통해 결과 값(Double)을 도출하여 결과창(UITextField)에 입력되도록 한다.
- 입력하는 것을 바로바로 인식하여 계산하고, 별도의 button event는 없다.
따라서 바인딩 코드는 다음 처럼 나올 것으로 예상한다
// ExchangeViewController.swift
viewModel.result.subscribe { value in
DispatchQueue.main.async {
self.resultQuantityTextField.text = value
}
}
ViewModel 작성
“바인딩”은 값이 변화하는 것을 감지하여 UI를 변경하기 때문에, result의 값이 변화하는 것을 관찰해야하므로 result의 자료형은 Observable type으로 지정한다.
// ExchangeViewModel.swift
class ExchangeViewModel {
var result: Observable<String> = Observable(resultQuantity)
이 때 Observable type은 Swift에서 기본적으로 제공하는 자료형이 아니므로 별도로 직접 정의를 해야한다.
// Observable.swift
class Observable<T> {
var value: T
init(_ value: T) {
self.value = value
}
}
- value: 변화를 관찰하고자 하는 값을 담을 변수이며 다양한 자료형일 수 있기 때문에 Generic type으로 지정
값이 변화하는 것을 관찰했으니 변화 할 때마다 발생될 특정 ‘특정 작업’을 didset
을 이용하여 적용시킬 것이다.
var value: T {
didSet {
// '특정 작업' 수행
}
}
그러기 위해서는 당연히 특정 ‘특정 작업’을 받을 변수도 필요하다. value를 받아서 void를 리턴하는 클로저를 선언하고 해당 변수에 ‘특정 작업’을 저장했다가 수행 할 수 있도록 한다.
var listener: ((T) -> Void)?
지금 까지 나온 것들을 종합하면,
// Observable.swift
class Observable<T> {
var value: T {
didSet {
// 값을 받아 '특정 작업' 수행
self.listener?(value)
}
}
private var listner: ((T) -> Void)?
init(_ value: T) {
self.value = value
}
}
‘특정 작업’를 담을 변수는 선언했으나 ‘특정 작업’을 정의할 부분을 아직 만들지 못했다. 맨 처음 바인딩하는 부분에서 사용되는 .subscribe{ }
를 통해 정의해줄 것이다.
이 함수가 갖는 역할은 2가지다.
- 특정 ‘특정 작업’를 실행
- 특정 ‘특정 작업’를 나중에 didSet에서도 실행할 수 있도록 클로저 변수(
listener
)에 저장
func subscribe(listener: @escaping (T) -> Void) {
listener(value) // '특정 작업' 실행
self.listener = listener // '특정 작업'을 didSet에서 실행하기 위해 저장
}
이 함수까지 모두 종합하면 Observable class가 완성된다.
// Observable.swift
class Observable<T> {
// value definition
var value: T {
didSet {
// 값을 받아 '특정 작업' 수행
self.listener?(value)
}
}
private var listner: ((T) -> Void)? // '특정 작업'을 저장 할 클로저 변수
init(_ value: T) {
self.value = value // value 초기화
}
func subscribe(listener: @escaping (T) -> Void) {
listener(value) // '특정 작업' 실행
self.listener = listener // '특정 작업'을 didSet에서 실행하기 위해 저장
}
}
Reference
[Swift] didSet과 closure를 통한 데이터 바인딩