Binder
바인딩에는 데이터 생산자(Producer)와 데이터 소비자(Consumer)가 있다.
- 생산자는 옵저버블이다. Observable 타입 전부가 다 생산자
- 소비자는 Label, ImageView, UIView 같은 UI Component
생산자가 생산한 데이터는 소비자에게 전달되어 데이터를 소비한다.
레이블은 전달 받은 텍스트를 레이블에 표시한다.
반대의 경우는 없다. (소비자가 데이터를 생산하는 경우는 없다는 것)
이게 말로 표현해둬서 어려울 수 있는데 코드를 보면서 빗대어 보면 이해가 쉽다. 그래서 앞뒤 맥락없이 Binder의 개념에 대한 설명없이 갑자기 코드를 들이밀고 설명을 해보자면
inputField.rx.editingDidEnd
.map { UIColor.gray }
.bind(to: inputField.rx.borderColor)
.disposed(by: bag)
위 코드를 간략하게 설명해보면,
inputField.rx.editingDidEnd 라는 옵저버블(생산자)은 map을 통해 inputField가 편집이 끝날 때 .gray라는 색상을 방출하고, 이걸 inputTextField.rx.borderColor에 binding해주고 있다. 즉, 생산자가 gray라는 데이터를 소비자에게 전달하는 위 설명의 맥락이 이런 것이다. 이때 데이터를 전달받은 소비자가 inputField.rx.borderColor라는 건데 이 옵저버는 Binder라고 부른다.
Binder는 UI Binding에 사용하는 특별한 옵저버이다. (데이터 소비자)
Binder의 특징은 UI Binding에 사용되기 때문에 그 점을 통해 기억해보면 되지 싶다.
- 에러 이벤트를 받지 않는다.
- Next Event를 받지 않으면 UI가 update되지 않는다.
- 바인딩이 성공하면 UI가 업데이트가 된다.
- 즉, Main Thread에서 실행된다. 따라서 아래에 나오겠지만 Binder에서는 따로 스케줄러를 지정해주지 않아도 된다.
Binder 구조를 살펴보자
public struct Binder<Value>: ObserverType {
...
}
- 제네릭 구조체로 만들어진 Binder
- ObserverType 프로토콜을 채택하고 있기에 새로운 값을 전달받을 수 있지만 옵저버라 구독자를 추가하는 건 불가능하다.
- 첫 번째 파라미터로 UI와 관련된 클래스만(AnyObject) 받는데 왜냐하면 UI Binding에 쓰이기 때문에
- 두 번째 파라미터로는 scheduler가 온다. MainScheduler를 쓴다. UI Binding에 사용하니까
- 세 번째 파라미터로인 클로저는 2개의 파라미터를 받는데, Target은 Binder가 확장하는 UI 객체, Value는 binder로 전달된 객체인데 --> value로 전달된 값을 target에 저장함
next event가 전달되는 시점마다 MainScheduler에서 클로저를 호출하고
error event가 전달되는 시점마다 rxFatalErrorInDebug 함수를 호출하고 있는데 살펴보면 DEBUG 모드에서는 fatalError를, RELEASE 모드에서는 print를 출력해 error event를 받지 않는 걸 알 수 있다.
completed event가 전달될 때는 아무런 처리를 하지 않는다. 왜냐면 종료되지 않기 때문임
예시를 살펴보자.
extension Reactive where Base: UILabel {
...
public var text: Binder<String?> {
return Binder(self.base) { label, text in
label.text = text
}
}
text 속성이 Binder로 선언되어 있고,
Binder를 생성하면서 첫 번째 파라미터로 UILabel을 전달하고 있고,
두 번째 파라미터는 메인 스케줄러로 생략되어 있고,
세 번째 파라미터를 트레일링 클로저로 전달하고 있다. 클로저에는 label과 Binder로 전달된 text가 파라미터로 전달되고 있다.
Custom Binder
Custom Binder를 만들 때도 위 예시를 그대로 적용한다.
UILabel에 CustomBinder를 추가해보자.
UILabel을 확장해주는 Reactive Extension을 추가한다.
새로운 속성으로 segementedValue로 타입을 Binder<Int>로 준다.
Binder의 첫 번째 파라미터로 base를 전달하면 UILabel이 전달될 거고
두 번째 파라미터는 스케줄러인데 Main Scheduler니까 생략
세 번째 파라미터는 트레일링 클로저로 label과 index가 전달된다.
클로저 내부는 index에 따라 text와 textColor가 바뀌도록 구현해준다.
extension Reactive where Base: UILabel {
var segementedValue: Binder<Int> {
return Binder(self.base) { label, index in
switch index {
case 0:
label.text = "Red"
label.textColor = UIColor.red
case 1:
label.text = "Green"
label.textColor = UIColor.green
case 2:
label.text = "Blue"
label.textColor = UIColor.blue
default:
label.text = "Unknown"
label.textColor = UIColor.black
}
}
}
}
colorPicker.rx.selectedSegmentIndex
.bind(to: valueLabel.rx.segementedValue)
.disposed(by: bag)
Binder 구현이 완료됐으니, selectedSegmentIndex를 Binder에 바인딩해준다.
'⭐️ 개발 > Rx' 카테고리의 다른 글
[Rx Operator 시리즈] 1. map (0) | 2023.01.16 |
---|---|
7. Traits - ControlProperty, ControlEvent, Driver (0) | 2023.01.16 |
[Rx] Error Operator (0) | 2023.01.16 |
[Rx] Input/Output 패턴 적용하기 - 비즈니스 로직 분리!! (4) | 2022.11.02 |
[Rx] TIL - Rx 개념 재복습 및 총정리 (0) | 2022.10.31 |