ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [RxSwift] ControlProperty와 ObservableType
    iOS 2024. 8. 5. 09:53

     

    이번 포스트에서는 UITextField.rx.text가 반환하는 ControlProperty 구조체와 ControlProperty가 준수하는 ObservableType에 대해 알아보면서 UITextField.rx.text가 subscribe를 사용할 수 있는 이유를 알아보자.

     

    이전 포스트에서 다루었던 것처럼 Reactive<UITextField> 구조체는 text 연산 프로퍼티를 통해 ControlProperty 구조체를 반환한다

     

    RxCocoa에 구현된 UITextField의 Reactive extension  

     

    extension Reactive where Base: UITextField {
        /// Reactive wrapper for `text` property.
        public var text: ControlProperty<String?> {
            value
        }
        
         /// Reactive wrapper for `text` property.
        public var value: ControlProperty<String?> {
            return base.rx.controlPropertyWithDefaultEvents(
                getter: { textField in
                    textField.text
                },
                setter: { textField, value in
                    // This check is important because setting text value always clears control state
                    // including marked text selection which is important for proper input
                    // when IME input method is used.
                    if textField.text != value {
                        textField.text = value
                    }
                }
            )
        }
        
        ......
        
    }

     

     

    위 소스코드의 실행순서를 정리하면 아래와 같다.

     

    UITextField.rx.text 
    • Reactive의 Extension으로 구현된 연산프로퍼티 text를 호출
    • 같은 Extension의 멤버인 연산프로퍼티 value를 재호출
    • getter와 setter파라미터에 textField 객체의 text 값을 가져오고 설정하는 클로저들을 매개변수로 넘기고  base.rx.controlPropertyWithDefaultEvents 메서드를 호출해 ControlProperty<String?> 반환

     

    UIControl 클래스를 반환하는 Reactive Extension

     

    import RxSwift
    import UIKit
    
    extension Reactive where Base: UIControl {
        /// Reactive wrapper for target action pattern.
        ///
        /// - parameter controlEvents: Filter for observed event types.
        public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> {
            let source: Observable<Void> = Observable.create { [weak control = self.base] observer in
                    MainScheduler.ensureRunningOnMainThread()
    
                    guard let control = control else {
                        observer.on(.completed)
                        return Disposables.create()
                    }
    
                    let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in
                        observer.on(.next(()))
                    }
    
                    return Disposables.create(with: controlTarget.dispose)
                }
                .take(until: deallocated)
    
            return ControlEvent(events: source)
        }
    
        /// Creates a `ControlProperty` that is triggered by target/action pattern value updates.
        ///
        /// - parameter controlEvents: Events that trigger value update sequence elements.
        /// - parameter getter: Property value getter.
        /// - parameter setter: Property value setter.
        public func controlProperty<T>(
            editingEvents: UIControl.Event,
            getter: @escaping (Base) -> T,
            setter: @escaping (Base, T) -> Void
        ) -> ControlProperty<T> {
            let source: Observable<T> = Observable.create { [weak weakControl = base] observer in
                    guard let control = weakControl else {
                        observer.on(.completed)
                        return Disposables.create()
                    }
    
                    observer.on(.next(getter(control)))
    
                    let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
                        if let control = weakControl {
                            observer.on(.next(getter(control)))
                        }
                    }
                    
                    return Disposables.create(with: controlTarget.dispose)
                }
                .take(until: deallocated)
    
            let bindingObserver = Binder(base, binding: setter)
    
            return ControlProperty<T>(values: source, valueSink: bindingObserver)
        }
    
        /// This is a separate method to better communicate to public consumers that
        /// an `editingEvent` needs to fire for control property to be updated.
        internal func controlPropertyWithDefaultEvents<T>(
            editingEvents: UIControl.Event = [.allEditingEvents, .valueChanged],
            getter: @escaping (Base) -> T,
            setter: @escaping (Base, T) -> Void
            ) -> ControlProperty<T> {
            return controlProperty(
                editingEvents: editingEvents,
                getter: getter,
                setter: setter
            )
        }
    }

     

     

     


    이전 포스트

     

    [RxSwift] UILabel이 subscribe를 사용할 수 없는 이유

    이 포스트를 읽으면 좋은 분들1. RxSwift의 작동방식을 알고 싶지만 어렵게 느껴지는 분들2. 기존에 subscribe메서드를 사용해왔지만 내부 구조를 살펴볼 여유가 없었던 분들    비동기 프로그래밍

    tunastorm.tistory.com

    댓글

All Posts are written by Tunastorm