이번엔 RxCocoa의 ControlProperty와 ControlEvent에 대해 알아보아요.
ControlProperty
ControlProperty는 Subject처럼 프로퍼티에 값을 주입할 수 있고 동시에 값의 변화도 관찰할 수 있는 타입입니다. 이는 (UIElement).rx
를 통해 접근할 수 있습니다. ControlProperty를 사용하면 해당하는 프로퍼티의 변경사항을 데이터 시퀀스로 받아올 수 있습니다.
아래 UITextField+Rx.Swift
의 구현 예시를 봅시다.
extension Reactive where Base: UITextField {
...
public var text: ControlProperty<String?> {
return value
}
...
}
내부에 존재하는 text가 ControlProperty 타입입니다. 이는 (UITextField).rx.text
로 접근할 수 있습니다. ControlProperty는 bind(to:)
메서드를 통해 subscribe를 수행할 수 있습니다. (bind(to:)
는 일종의 syntax suger로, 사용자에게 좀 더 직관적인 이해를 제공합니다. 내부적인 처리는 subscribe와 동일합니다.)
여기서 잠깐! ControlProperty와 함께 많이 활용되는 Binder라는 개념도 존재합니다.
Binder
RxCocoa에는 내부 property를 Observer처럼 제공해 변경사항을 이에 바로 적용할 수 있도록 하는 기능도 존재합니다. 이를 Binder라고 합니다.
UILabel+Rx.Swift
의 구현 내용을 봅시다.
extension Reactive where Base: UILabel {
...
public var text: Binder<String?> {
return Binder(self.base) { label, text in
label.text = text
}
}
내부에서 Binder
라는 타입을 사용하고 있습니다. 데이터를 받아오면 이를 바로 UI Elements에 적용합니다.
public struct Binder<Value>: ObserverType {
...
}
Binder
타입의 정의를 보니 이 역시 ObserverType의 일종임을 확인할 수 있네요.
어느정도 이해가 되셨나요? ControlProperty와 Binder를 활용해 데이터 시퀀스의 반환 및 적용하는 예시를 보겠습니다.
let results = query.rx.text
.throttle(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
}
results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)
query.rx.text
라는 ControlProperty를 통해 fetchAutoCompleteItems
에 query를 날리고 이 반환값을 resultCount.rx.text
라는 binder에 적용하고 있습니다.
ControlEvent
Cocoa에서는 수많은 Event가 발생합니다. 그 이벤트는 ViewController가 Load되면 발생하는 ViewDidLoad에 대한 메세지가 될 수도 있고, User로 인해 발생하는 Tap Event가 될 수도 있습니다. ControlEvent은 이러한 Event들에 대한 시퀀스를 받아올 수 있는 기능입니다.
아래 UICollectionView+Rx
의 구현 예시를 봅시다.
extension Reactive where Base: UICollectionView {
/// Reactive wrapper for `delegate` message `collectionView:didSelectItemAtIndexPath:`.
public var itemSelected: ControlEvent<IndexPath> {
let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:)))
.map { a in
return a[1] as! IndexPath
}
return ControlEvent(events: source)
}
}
내부에서 itemSelected가 ControlEvent라는 타입을 사용하고 있습니다. 이는 (UICollectionView).rx.itemSelected
로 접근할 수 있습니다. 이 역시 ControlProperty와 같이 bind하여 필요한 작업을 수행할 수 있습니다.
RxCocoa Traits?
지난 게시글에서 RxCocoa의 Trait에 대해 알아보았는데요. 사실 ControlProperty와 ControlEvent도 Trait의 일종이라고 합니다. RxSwift의 문서에서도 Trait으로 소개하고 있어요!
https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md#rxcocoa-traits
그런데 이렇게되면 조금 헷갈리는게... Control Property는 Subject와 유사한 역할을 하고 있는데, 그럼 Subject도 Trait의 일종으로 볼 수 있는 것일까요? Subject 역시 Observable을 wrapping하고 있으니까요...
이런 개념의 분리가 중요한 것은 아니겠지만 확실하게 어떻게 생각하면 좋을지 알고 싶네요. 혹시 아시는 분이 있다면 댓글 부탁드립니다!
참고
- https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md
- https://jusung.github.io/RxSwift-Section12/
'🍎 Apple > Combine & Rx' 카테고리의 다른 글
[RxSwift] Relay (PublishRelay, BehaviorRelay, ReplayRelay) (0) | 2022.02.25 |
---|---|
[RxSwift] Driver, Signal (0) | 2022.02.24 |
[RxSwift] Trait (Single, Completable, Maybe) (2) | 2022.02.24 |
[RxSwift] Scheduler (2) | 2022.02.24 |
[RxSwift] Subject (0) | 2022.02.16 |