[Swift] didSet과 closure를 통한 데이터 바인딩

RXSwift 없이 view와 viewModel 을 바인딩 해보자

naljin
6 min readOct 22, 2021

들어가기 전에

RxSwift 사용 안하고 view와 viewModel 을 어떻게 바인딩 할까요,,?

아래처럼 사용해서 title 값 바뀔때마다 label 에 값 설정 해주고 싶은디요??

viewModel.title.subscribe { value in
DispatchQueue.main.async {
self.titleLabel.text = value
}
}

흠,, 옛날 옛적의 기억으로는didSet 이용했던거 같은데,,? ㅎㅎ 역시 정리를 안하니 백날 천날 까먹기만 하는군!

정리를 하다보니 오늘의 컨셉은 문답 형식이 됐군여 ㅋㅎ 튼 시작해보자구여 ㄱㄱ

Observable 로 가는 여정 🏃🏻‍♀️

😎 일단 뷰 모델을 만들어봅시다. 여기서 title 의 타입은 뭐가 될까여??

🤔 흠 모르긴 몰라도 title.subsribe{} 처럼 써야하니 title 이 단순히 String 타입은 아니겠지?

😎 마자여. 값을 관찰할 수 있다는 뜻의 Observable Type 을 사용해보자구요. 우리는 title 값의 변화를 계속 관찰할거니까요!

class MyViewModel {
var title: Observable<String> = Observable("Welcome")
}

🤔 ??? 갑자기 어디서 나온 Observable ??

😎 ㅎㅎ 물론 제가 만든거구요,,? 하나씩 만들어 가봅시다

class Observable<T> {
var value: T

init(_ value: T) {
self.value = value
}
}

실제 값이 들어갈 value 변수를 만들고 type은 Generic type 으로 설정을 했습니다.

🤔 흠,,, 알겠고,, 이 value 가 변경될때마다 어떤 행동(ex. label 같은데 할당하든지, 값을 print를 하든지..)을 수행하고 싶은데??

😎 그럴땐 didSet 을 사용하면 됩니다! 속성의 값이 설정될 때마다 호출되는 친구니까요!!

var value: T {
didSet {
//값을 받아서 특정 행동 수행
}
}

🤔 근데 저 시점에서 ‘특정 행동’ 이 뭔지 어떻게 아는데???

😎 어디선가 ‘특정 행동’을 받아서 저장해뒀다가 이걸 didSet 에서 시행하면 되겠죠??

🤔 오호,, 이렇게 value type 를 받고 void 를 return 하는 클로저를 선언해서 여기에 ‘특정 행동’을 담아뒀다가 실행하면 되겠네??

var listener: ((T) -> Void)?

😎 맞습니다. 코드는 이렇게 될거예요

class Observable<T> {
var value: T {
didSet {
//값을 받아서 특정 행동 수행
self.listener?(value)
}
}

var listener: ((T) -> Void)?
}

🤔 ㅇㅋㅇㅋ 그럼 ‘특정 행동’을 받아서 저장하는건 어디서 하지??

😎 해당 값을 구독 하는 시점에 저장하면 되죠! 우리가 값을 구독할때 .subscribe{} 를 사용했던게 기억 나시나여?

viewModel.title.subscribe { value in
DispatchQueue.main.async {
self.titleLabel.text = value
}
}

이렇게 사용할 수 있도록 subscribe 함수를 만들어 봅시다! 이 함수의 역할은 두가지가 될거예요

  1. ‘특정 행동’ 실행
  2. ‘특정 행동’ 을 나중에 didSet 에서도 실행할 수 있도록 클로저 변수에 담아두기
func subscribe(listener: @escaping (T) -> Void) {
listener(value) // 1. '특정 행동' 실행
self.listener = listener // 2. '특정 행동' 을 나중에 didSet 에서도 실행할 수 있도록 클로저 변수에 담아두기
}

자자 이제 끝났어요!! Observable 전체 코드를 볼까여?

class Observable<T> {
//1-1. 값 담을 value 만듦
var value: T {
//2. didSet 생성
didSet {
//4. 값이 변경 될 때마다 listener 에 담겨있는 행동 수행
self.listener?(value)
}
}

//3. 값이 변경 될 때마다 수행될 행동을 담고 있는 클로저 변수 생성
var listener: ((T) -> Void)?

//1-2. init 생성
init(_ value: T) {
self.value = value
}

//5. 구독을 통해 파라미터로 넘어온 '특정 행동'을 수행하고, 나중에 didSet 에서도 실행할 수 있도록 클로저 변수에 담기
func subscribe(listener: @escaping (T) -> Void) {
listener(value)
self.listener = listener
}
}

그럼 이제 뷰컨에서 이런식으로 쓸 수 있다구요?

viewModel.title.subscribe { value in
DispatchQueue.main.async {
self.titleLabel.text = value
}
}

이해가 되셨을까여?!?!!

마무리

기본 Swift 로 바인딩을 구현하는 다른 레퍼런스들을 보면 Observable 내에 Observer를 따로 만들어서 array 로 담아두었다가, value가 변경될 때마다 for 문을 돌면서 각 observerlistener 를 실행하기도 하지만,,!

위의 내용은 하단 참고에서 2,3번째 링크를 보기로 하고, 일단 이번 포스팅에서는 기본 개념만을 다루어보았습니다. 이 내용을 이해하면 다른 것도 이해할 수 있을 거라고 생각해서,,?!

그럼 ViewController, ViewModel, Observable 이 포함된 전체 코드와 짤을 던지며 전 이만 사라지겠슴둥! ㅃㅇ!

참고

--

--