ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [RxSwift] Binder와 ObserverType
    iOS 2024. 8. 10. 16:21

     

    이번 포스트에서는  UILabel이 subscribe 메서드를 사용할 수 없는 직접적인 이유를 알아보기 위해 Reactvie 구조체가 반환하는 Binder 구조체와 Binder가 준수하는 ObserverType 프로토콜에 대해 살펴보려 한다.

     

    먼저 Binder구조체의 소스코드를 살펴보면 이는 최종적으로 스스로 Event를 Emit할 수 있는 Observer를 반환하는 역할을 지닌 객체임을 알 수 있다.

     

    Binder 구조체

     

    public struct Binder<Value>: ObserverType {
        public typealias Element = Value
        
        private let binding: (Event<Value>) -> Void
    
        /// Initializes `Binder`
        ///
        /// - parameter target: Target object.
        /// - parameter scheduler: Scheduler used to bind the events.
        /// - parameter binding: Binding logic.
        public init<Target: AnyObject>(_ target: Target, scheduler: ImmediateSchedulerType = MainScheduler(), binding: @escaping (Target, Value) -> Void) {
            weak var weakTarget = target
    
            self.binding = { event in
                switch event {
                case .next(let element):
                    _ = scheduler.schedule(element) { element in
                        if let target = weakTarget {
                            binding(target, element)
                        }
                        return Disposables.create()
                    }
                case .error(let error):
                    rxFatalErrorInDebug("Binding error: \(error)")
                case .completed:
                    break
                }
            }
        }
    
        /// Binds next element to owner view as described in `binding`.
        public func on(_ event: Event<Value>) {
            self.binding(event)
        }
    
        /// Erases type of observer.
        ///
        /// - returns: type erased observer.
        public func asObserver() -> AnyObserver<Value> {
            AnyObserver(eventHandler: self.on)
        }
    }


    Reacitve의 subscript 연산자 내부에서 Binder(base.self)를 실행할 때 넘겨주는 클로저는 생성자 내부에서 binding(target, element)로 호출된다

     

    RxSwift.Reactive

     

    @dynamicMemberLookup
    public struct Reactive<Base> {
        /// Base object to extend.
        public let base: Base
    
        /// Creates extensions with base object.
        ///
        /// - parameter base: Base object.
        public init(_ base: Base) {
            self.base = base
        }
    
        /// Automatically synthesized binder for a key path between the reactive
        /// base and one of its properties
        public subscript<Property>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Property>) -> Binder<Property> where Base: AnyObject {
            /// Binder의 생성자를 호출하면서 binding(target, value) 클로저를 넘겨주고 있다
            Binder(self.base) { base, value in 
                base[keyPath: keyPath] = value
            }
        }
    }

     

    또한 Reactive의 내부에서 binding(target, element)가 호출되는 클로저는 생성시 바로 실행되는 것이 아니라 Reactive 구조체의 binding 저장 프로퍼티에 할당된다.

     

    이후 값의 타입이 일치하는 Event 객체가 on메서드의 매개변수로 전달될 때 자기 자신의 Observable의 역할을 하며 실행(subscribe)되도록 설계되어 있다.

     

    비록 Binder 구조체 자체는 ObserverType 프로토콜을 준수하고 있지만 맴버인 binding 프로퍼티에 할당된 클로저가 Observable의 기능과 동일하게 Event를 Emit하고 Disposable 구조체를 반환하므로 마치 생성시 values 프로퍼티의 초기화가 실행되는 BehaviorSubject와 유사한 로직으로 작동하게 된다.

     

    여기서 on 메서드는 Binder 구조체에 구현되어있지만 본래는 Binder가 준수하고 있는 ObserverType 프로토콜에 정의된 추상메서드이며 이 구현으로 Binder구조체는 Observer를 생성해 반환하는 객체로 정의된 것이다.

     

     

    ObserverType 프로토콜

     

    public protocol ObserverType {
        /// The type of elements in sequence that observer can observe.
        associatedtype Element
    
        /// Notify observer about sequence event.
        ///
        /// - parameter event: Event that occurred.
        func on(_ event: Event<Element>)
    }
    
    /// Convenience API extensions to provide alternate next, error, completed events
    extension ObserverType {
        
        /// Convenience method equivalent to `on(.next(element: Element))`
        ///
        /// - parameter element: Next element to send to observer(s)
        public func onNext(_ element: Element) {
            self.on(.next(element))
        }
        
        /// Convenience method equivalent to `on(.completed)`
        public func onCompleted() {
            self.on(.completed)
        }
        
        /// Convenience method equivalent to `on(.error(Swift.Error))`
        /// - parameter error: Swift.Error to send to observer(s)
        public func onError(_ error: Swift.Error) {
            self.on(.error(error))
        }
    }

     

    onNext, onComplete, onError 메서드 모두 on 메서드에 각각의 Event enum을 매개변수로 넘기고 이 때 호출되는 on 메서드는 ObserverType을 준수하는 구현객체 내부에 구현된 on 메서드이다.

     

    그러므로 Binder 구조체 역시 onNext, onComplete, onError메서드를 호출해 자신의 binding프로퍼티에 할당된 클로저의 내부에서 각 이벤트에 따른 이후의 로직을 분기처리할 수 있게 된다.

     

    또한 Binder구조체를 AnyObserver 구조체로 변환하는 asObserver 메서드와 mapObserver 메서드의 경우 아래와 같이 ObserverType 프로토콜의 Extension에 이미 구현되어 있다.

     

    AnyObserver를 반환하는 ObserverType Extension

     

    public struct AnyObserver<Element> : ObserverType {
        /// Anonymous event handler type.
        public typealias EventHandler = (Event<Element>) -> Void
    
        private let observer: EventHandler
    
        /// Construct an instance whose `on(event)` calls `eventHandler(event)`
        ///
        /// - parameter eventHandler: Event handler that observes sequences events.
        public init(eventHandler: @escaping EventHandler) {
            self.observer = eventHandler
        }
        
        /// Construct an instance whose `on(event)` calls `observer.on(event)`
        ///
        /// - parameter observer: Observer that receives sequence events.
        public init<Observer: ObserverType>(_ observer: Observer) where Observer.Element == Element {
            self.observer = observer.on
        }
        
        /// Send `event` to this observer.
        ///
        /// - parameter event: Event instance.
        public func on(_ event: Event<Element>) {
            self.observer(event)
        }
    
        /// Erases type of observer and returns canonical observer.
        ///
        /// - returns: type erased observer.
        public func asObserver() -> AnyObserver<Element> {
            self
        }
    }
    
    extension AnyObserver {
        /// Collection of `AnyObserver`s
        typealias s = Bag<(Event<Element>) -> Void>
    }
    
    extension ObserverType {
        /// Erases type of observer and returns canonical observer.
        ///
        /// - returns: type erased observer.
        public func asObserver() -> AnyObserver<Element> {
            AnyObserver(self)
        }
    
        /// Transforms observer of type R to type E using custom transform method.
        /// Each event sent to result observer is transformed and sent to `self`.
        ///
        /// - returns: observer that transforms events.
        public func mapObserver<Result>(_ transform: @escaping (Result) throws -> Element) -> AnyObserver<Result> {
            AnyObserver { e in
                self.on(e.map(transform))
            }
        }
    }

     

    지금까지 살펴본 바와 같이 Binder 구조체, ObserverType 프로토콜과 그 구현객체들 중 어디에도 subscribe 메서드가 구현되어 있지 않다.

     

    그러므로 어떤 UI 객체를 Element프로퍼티에 할당한 Reactive 구조체가 Binder 구조체를 반환하는 경우 Observable Stream은 Observer에 도달하게 되어 subscribe 메서드를 사용할 수 없는 상태가 된다.

     

    UILabel의 경우에는 아래 이미지처럼 RxCocoa에 Reactive의 extension이 구현되어 있지 않기에  Reactive<UILabel> 구조체는 ControlProperty구조체를 반환할 수 없다. 

     

    RxCocoa에 구현된 각 UI 객체별 Reactive Extension들

     

    UITextField의 Reactive Extension은 존재하지만 UILabel은 그렇지 않다

     

    따라서 자연스럽게 Reactive 구조체에 기본적으로 구현되어 있는 Binder 구조체만 반환할 수 있게 되어 Binder구조체와 ObserberType 구조체에 구현된 메서드들만 호출할 수 있다.

     

    UILabel.rx.text로 반환받은 Binder<String> 구조체의 메서드들

     

    이제 이 메서드들이 각자 어느 구조체의 멤버들인지 알게 되었을 것이다!

     

     

    이번 시리즈의 주제인 UILabel이 subscribe를 사용할 수 없는 이유는 UILabel은 RxCocoa에서 자신을 위해 구현되어 있지 않은 ObservableType프로토콜 구현객체의 멤버인 subscribe 메서드를 사용할 수 없기 때문이다는 것이 결론이다.

     


    이전 포스트

     

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

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

    tunastorm.tistory.com

     

     

    댓글

All Posts are written by Tunastorm