RxSwift) Trait(2) ControlProperty, ControlEvent

2 minute read

RxCocoa traits

RxSwift 문서를 정리 및 요약해보겠습니다. (출처 - RxCocoa Traits)

ControlProperty 와 ControlEvent 는 UI 요소의 속성을 나타내는 Observable/ObservableType 의 trait 입니다.

👉 ControlProperty

ControlProperty 는 ObservableType 과 ObserverType 을 동시에 채택하는 ControlPropetyType 을 채택합니다.

Subject 와 같이 프로퍼티에 새로운 값을 관찰할 수도 방출할 수도 있습니다.

public struct ControlProperty<PropertyType> : ControlPropertyType {
    ...
}

/// Protocol that enables extension of `ControlProperty`.
public protocol ControlPropertyType : ObservableType, ObserverType {

    /// - returns: `ControlProperty` interface
    func asControlProperty() -> ControlProperty<Element>
}

예를 들어, UITextField+Rx.Swift 에서는 다음과 같이 text 를 ControlProperty 로 가집니다.

extension Reactive where Base: UITextField { ...
    public var text: ControlProperty<String?> {
        return value
    }
...
}

Observable 채택한 text 에 대한 변경사항을 관찰하기 위해서 subscribe 를 수행할 수 있습니다.

ObserverType 채택하였기 때문에 다음과 같이 사용할 수 있습니다.

drive(_:) 와 bind(to:) 는 ObserverType 을 채택한 객체를 파라미터로 전달할 수 있습니다.

.drive(textfield.rx.text)

.bind(to: textfield.rx.text)

위의 예시처럼 ControlProperty 는 시퀀스가 오직 초기 control value 와 사용자가 시작한 값 변경만을 나타냅니다.

속성은 다음과 같습니다.

  • never fails
  • never errors out
    • UI 작업에 특화되있는 RxCocoa 의 Trait 이기 때문에 스트림이 끊기지 않도록 실패를 하지 않도록 합니다.
  • share(replay: 1) behavior
    • 구독 시에 마지막 요소가 있는 경우 즉시 replay 됩니다.
  • 할당 해제될때 Complete 방출됩니다.
  • MainScheduler.instance 에 이벤트를 전달합니다.
    • ControlEvent 의 구현은 이벤트 시퀀스가 main scheduler(subscribeOn(ConcurrentMainScheduler.instance) 동작)에서 구독되도록 합니다.

좀 더 예시 코드를 살펴보겠습니다. UISearchBar+Rx.swift 에서는 text 를

 extension Reactive where Base: UISearchBar {
    /// Reactive wrapper for `text` property.
    public var value: ControlProperty<String?> {
        let source: Observable<String?> = Observable.deferred { [weak searchBar = self.base as UISearchBar] () -> Observable<String?> in
            let text = searchBar?.text
            
            return (searchBar?.rx.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBar(_:textDidChange:))) ?? Observable.empty())
                    .map { a in
                        return a[1] as? String
                    }
                    .startWith(text)
        }

        let bindingObserver = Binder(self.base) { (searchBar, text: String?) in
            searchBar.text = text
        }
        
        return ControlProperty(values: source, valueSink: bindingObserver)
    }
}

UISegmentedControl 에서는 selectedSegmentIndex 를 래핑하고 있습니다.

extension Reactive where Base: UISegmentedControl {
    /// Reactive wrapper for `selectedSegmentIndex` property.
    public var selectedSegmentIndex: ControlProperty<Int> {
        value
    }
    
    /// Reactive wrapper for `selectedSegmentIndex` property.
    public var value: ControlProperty<Int> {
        return UIControl.rx.value(
            self.base,
            getter: { segmentedControl in
                segmentedControl.selectedSegmentIndex
            }, setter: { segmentedControl, value in
                segmentedControl.selectedSegmentIndex = value
            }
        )
    }
}

👉 ControlEvent

ControlEventObservableType 을 채택하는 ControlEventType 을 채택합니다.

public struct ControlEvent<PropertyType> : ControlEventType {
    ...
}

/// A protocol that extends `ControlEvent`.
public protocol ControlEventType : ObservableType {

    /// - returns: `ControlEvent` interface
    func asControlEvent() -> ControlEvent<Element>
}

속성은 다음과 같습니다.

  • never fails
  • never errors out
    • UI 작업에 특화되있는 RxCocoa 의 Trait 이기 때문에 스트림이 끊기지 않도록 실패를 하지 않도록 합니다.
  • 구독 시 초기 값을 보내지 않습니다.
  • 할당 해제될때 Complete 방출됩니다.
  • MainScheduler.instance 에 이벤트를 전달합니다.
    • ControlEvent 의 구현은 이벤트 시퀀스가 main scheduler(subscribeOn(ConcurrentMainScheduler.instance) 동작)에서 구독되도록 합니다.

예를 들어, UIButton+Rx.Swift

extension Reactive where Base: UIButton {
    
    /// Reactive wrapper for `TouchUpInside` control event.
    public var tap: ControlEvent<Void> {
        controlEvent(.touchUpInside)
    }
}

UICollectionView+Rx.swift 에서도 확인할 수 있습니다.

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)
    }
}

tap, itemSelected 의 변경사항을 관찰하기 위해서 subscribe 을 사용할 수 있습니다.

출처 :

https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md

[RxSwift] ControlProperty, ControlEvent

https://jusung.github.io/shareReplay/

Categories:

Updated: