How Does DelegateProxy work in RxSwift?

How Does DelegateProxy work in RxSwift?

DelegateProxy is a core feature in Rxswift to wrap the return value from original delegate to observable sequence. So it's important to discover how it works in RxSwift. Under the hood, message forwarding, a feature in Objective-C, plays a role in forwarding message from proxy to real delegate. You should have some basic knowledges about Objective-C Runtime before you go deeper. You can learn that in this post (Chinese version): Objective-C Runtime详解.

Use Cases

There are two ways to apply DelegateProxy to your app in different scenarios:

  • Delegate method has a return value.
  • Delegate method hasn't a return value, in other words, the type of method is void.

Here are some reasons about why we need differential measure to the two cases above, which I quoted from RxSwift Reactive Programming with Swift:

  • Delegate methods with a return type are not meant for observation, but for customization of the behavior.
  • Defining an automatic default value which would work in any case is a non-trivial task.

HAS NOT a Return Value

We use sentMessage(Selector) or methodInvoked(Selector) to establish a relationship between a selector which represents the method in Delegate you want to invoke and a observable sequence. The difference between those methods is whether elements are sent before message forwarding or not. More details about that will be introduced in the later.

HAS a Return Value

As we discussed before, a method that has a return value is not easy to be wrapped to observable, therefore we need to forward the invocation to real delegate. Suppose you want to MapViewDelegate work with RxSwift, you have a file named MKMapView+Rx.swift for making MapViewDelegate reactive and a file named ViewController.swift which conforms to MKMapViewDelegate as usual.

// MKMapView+Rx.swift
// `delegate` is an instance of DelegateProxy

// install a forwarding delegate
func setDelegate(_ delegate: MKMapViewDelegate) -> Disposable {
    return RxMKMapViewDelegateProxy.installForwardDelegate(
    delegate, 
    retainDelegate: false, 
    onProxyForObject: self.base
    )
 }

Then, you can call this function to set up with the real delegate instance.

// ViewController.swift

class ViewController {
  // ...
  // register forwarding delegate
  mapView.rx.setDelegate(self).disposed(by: bag)
  //...
}

extension ViewController: MKMapViewDelegate {
  // ...
  public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    guard let overlay = overlay as? ApiController.Weather.Overlay else {
      return MKOverlayRenderer()
    }
    let overlayView = ApiController.Weather.OverlayView(overlay: overlay, overlayIcon: overlay.icon)
    return overlayView
  }
  // ...
}

You may confuse after viewing this code. Don't worry! You can understand them better after you learned how forwarding delegate works.

Registering a Proxy

// TODO: Why we should register a proxy?

According to the source code, we can find all methods related to register is in DelegateProxyType.swift. Let me show you how we register a proxy in an extension of ParentObject first.

// Rx/RxCocoa/Common/DelegateProxyType.swift

public protocol DelegateProxyType: class {
  // ...
  // abstract static method
static func registerKnownImplementations()
// `DelegateProxy.identifer` is, in fact, an identifier of Delegate.
public static var identifier: UnsafeRawPointer { get }
  // ...
}

extension DelegateProxyType {
  // Commentted by Xavier: please refer to DelegateProxyFactory.extend(make:)
public static func register<Parent>(make: @escaping (Parent) -> Self) {
    self.factory.extend(make: make)
}
  // ...
}

extension DelegateProxyType {
private static var factory: DelegateProxyFactory {
    DelegateProxyFactory.sharedFactory(for: self)
}
  // ...
}

private class DelegateProxyFactory {
  private static var _sharedFactories: [UnsafeRawPointer: DelegateProxyFactory] = [:]

    fileprivate static func sharedFactory<DelegateProxy: DelegateProxyType>(for proxyType: DelegateProxy.Type) -> DelegateProxyFactory {
        MainScheduler.ensureRunningOnMainThread()
        let identifier = DelegateProxy.identifier
        if let factory = _sharedFactories[identifier] {
            return factory
        }
        let factory = DelegateProxyFactory(for: proxyType)
        _sharedFactories[identifier] = factory
        DelegateProxy.registerKnownImplementations()
        return factory
    }

    private var _factories: [ObjectIdentifier: (AnyObject) -> AnyObject]
    private var _identifier: UnsafeRawPointer

    // ...
  // Commentted by Xaiver: please refer to DelegateProxyType.register(make:)
    fileprivate func extend<DelegateProxy: DelegateProxyType, ParentObject>(make: @escaping (ParentObject) -> DelegateProxy) {
        MainScheduler.ensureRunningOnMainThread()
        precondition(_identifier == DelegateProxy.identifier, "Delegate proxy has inconsistent identifier")
        guard _factories[ObjectIdentifier(ParentObject.self)] == nil else {
            rxFatalError("The factory of \(ParentObject.self) is duplicated. DelegateProxy is not allowed of duplicated base object type.")
        }
        _factories[ObjectIdentifier(ParentObject.self)] = { make(castOrFatalError($0)) }
    }
  // ...
}

Notes

  • Relations that map a Delegate to an instance of DelegateProxyFactory are saved in _sharedFactories, and relations that map a ParentObject to Proxy for each Delegate are saved in _factories, an instance variable in DelegateProxyFactory.

  • Method saving the relations mentioned above is sharedFactory(for: DelegateProxy.Type) -> DelegateProxyFactory and extend(make: @escaping (ParentObject) -> DelegateProxy). It's easy to understand what happens if you neglect the code for log.

You override registerKnownImplementations() in the root of proxy and register all proxies corresponding to views that are inherited from root, since createProxy(_:) in the factory creates proxy is based on what type of object you pass and root proxy shares the same variable, e.g. xxx.rx.delegate, with their children.

For example, we know that the hierarchical relation in UITableView is: UITableView > UIScrollView > UIView.

// RxScrollViewDelegateProxy.swift
public static func registerKnownImplementations() {
    self.register { RxScrollViewDelegateProxy(scrollView: $0) }
    self.register { RxTableViewDelegateProxy(tableView: $0) }
    self.register { RxCollectionViewDelegateProxy(collectionView: $0) }
    self.register { RxTextViewDelegateProxy(textView: $0) }
}

In this case, UIScrollView is root, of course, its corresponding proxy, RxScrollViewDelegateProxy, is root as well. Therefore, in registerKnownImplementations() it registers all proxies of children, including RxTableViewDelegateProxy. And now you glance through the source code of UIScrollView+Rx.swift and UITableView+Rx.swift.

// UIScrollView+Rx.swift
public var delegate: DelegateProxy<UIScrollView, UIScrollViewDelegate> {
    return RxScrollViewDelegateProxy.proxy(for: base)
}

Although there is no definition about delegate in UITableView+Rx.swift, user could use self.delegate to call methods in methods of delegate.

// UITableView+Rx.swift
public var itemSelected: ControlEvent<IndexPath> {
    let source = delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:)))
        .map { a in
            try castOrThrow(IndexPath.self, a[1])
        }
    return ControlEvent(events: source)
}

The reason is that RxScrollViewDelegateProxy.proxy(for:) calls createProxy(_:) defined in DelegateProxyFactory finally. createProxy(_:), however, creates proxy depends on what type of object you passed in. For example, you call delegate in UITableView+Rx.swift, base passing to RxScrollViewDelegateProxy.proxy is an instance of UITableView, then the return type of proxy from createProxy(_:) is RxTableViewDelegateProxy.

Here is a flowchart to show you the process about registration.

cPv2wV-Ch3K0F

Creating a Proxy

As usual, we extend a class to create a proxy and use it as Delegate. To create a proxy, we perform proxy(object: ParentObject) to get an instance of DelegateProxy from the factory of DelegateProxy.

public extension Reactive where Base: CLLocationManager {
    var delegate: DelegateProxy<CLLocationManager, CLLocationManagerDelegate> {
        return RxCLLocationManagerDelegateProxy.proxy(for: base)
    }
}

Although creating a proxy could be done in less than three lines of code, RxSwift really does a lot for implementing those functions. The following codes scattered all over the place show you all related stuffs to this process.

Protocol: DelegateProxyType.swift

extension DelegateProxyType {
    // ...
    public static func proxy(for object: ParentObject) -> Self {
        MainScheduler.ensureRunningOnMainThread()

        let maybeProxy = assignedProxy(for: object)

        let proxy: AnyObject
        if let existingProxy = maybeProxy {
            proxy = existingProxy
        } else {
            proxy = castOrFatalError(createProxy(for: object))
            assignProxy(proxy, toObject: object)
            assert(assignedProxy(for: object) === proxy)
        }
        let currentDelegate = _currentDelegate(for: object)
        let delegateProxy: Self = castOrFatalError(proxy)

        if currentDelegate !== delegateProxy {
          // save the forwarded delegate in `__forwardToDelegate` defined 
          // in `_RXDelegateProxy.m`
            delegateProxy._setForwardToDelegate(currentDelegate, retainDelegate: false)
            assert(delegateProxy._forwardToDelegate() === currentDelegate)
            // set current proxy as delegate of object 
            _setCurrentDelegate(proxy, to: object)
            assert(_currentDelegate(for: object) === proxy)
            assert(delegateProxy._forwardToDelegate() === currentDelegate)
        }

        return delegateProxy
    }
  // ...
}

extension DelegateProxyType {
    static func _currentDelegate(for object: ParentObject) -> AnyObject? {
        currentDelegate(for: object).map { $0 as AnyObject }
    }

    static func _setCurrentDelegate(_ delegate: AnyObject?, to object: ParentObject) {
        setCurrentDelegate(castOptionalOrFatalError(delegate), to: object)
    }

    func _forwardToDelegate() -> AnyObject? {
        self.forwardToDelegate().map { $0 as AnyObject }
    }

    func _setForwardToDelegate(_ forwardToDelegate: AnyObject?, retainDelegate: Bool) {
      // setForwardToDelegate is defined in DelegateProxy.swift
        self.setForwardToDelegate(castOptionalOrFatalError(forwardToDelegate), retainDelegate: retainDelegate)
    }
}

extension DelegateProxyType where ParentObject: HasDelegate, Self.Delegate == ParentObject.Delegate {
    public static func currentDelegate(for object: ParentObject) -> Delegate? {
        object.delegate
    }
    public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
        object.delegate = delegate
    }
}

extension DelegateProxyType {
  // ...
    public static func createProxy(for object: AnyObject) -> Self {
        castOrFatalError(factory.createProxy(for: object))
    }
  // ...
}

private class DelegateProxyFactory {
  // ...
    fileprivate func createProxy(for object: AnyObject) -> AnyObject {
        MainScheduler.ensureRunningOnMainThread()
        var maybeMirror: Mirror? = Mirror(reflecting: object)
        while let mirror = maybeMirror {
            if let factory = _factories[ObjectIdentifier(mirror.subjectType)] {
                return factory(object)
            }
            maybeMirror = mirror.superclassMirror
        }
        rxFatalError("DelegateProxy has no factory of \(object). Implement DelegateProxy subclass for \(object) first.")
    }
}

Class: DelegateProxy.swift

open class DelegateProxy<P: AnyObject, D>: _RXDelegateProxy {
  // ...
  private var _sentMessageForSelector = [Selector: MessageDispatcher]()
    private var _methodInvokedForSelector = [Selector: MessageDispatcher]()

    open func setForwardToDelegate(_ delegate: Delegate?, retainDelegate: Bool) {
        #if DEBUG // 4.0 all configurations
            MainScheduler.ensureRunningOnMainThread()
        #endif
      // _setForwardToDelegate is defined in _RXDelegateProxy.m
        _setForwardToDelegate(delegate, retainDelegate: retainDelegate)

        let sentSelectors: [Selector] = _sentMessageForSelector.values.filter { $0.hasObservers }.map { $0.selector }
        let invokedSelectors: [Selector] = _methodInvokedForSelector.values.filter { $0.hasObservers }.map { $0.selector }
        let allUsedSelectors = sentSelectors + invokedSelectors

        for selector in Set(allUsedSelectors) {
            checkSelectorIsObservable(selector)
        }
        reset()
    }

  fileprivate func reset() {
        guard let parentObject = _parentObject else { return }

      // definiation in init(): self._currentDelegateFor = delegateProxy._currentDelegate
        let maybeCurrentDelegate = _currentDelegateFor(parentObject)

        if maybeCurrentDelegate === self {
          // definiation in init(): self._setCurrentDelegateTo = delegateProxy._setCurrentDelegate
            _setCurrentDelegateTo(nil, parentObject)
            _setCurrentDelegateTo(castOrFatalError(self), parentObject)
        }
    }
  // ...
}

Class: _RXDelegateProxy.m

id __weak __forwardToDelegate;

-(void)_setForwardToDelegate:(id __nullable)forwardToDelegate retainDelegate:(BOOL)retainDelegate {
    __forwardToDelegate = forwardToDelegate;
    if (retainDelegate) {
        self.strongForwardDelegate = forwardToDelegate;
    }
    else {
        self.strongForwardDelegate = nil;
    }
}

Notes:

  • checkSelectorIsObservable(selector:) does nothing, but just outputs some warning message.

Wrapping Delegate Methods to Observable

At the beginning, let us recap how does delegate work. A class, we refer to as ClassA, sends message via delegate, we call it DelegateA, to another class, ClassB, who conforms to the delegate protocol. The delegate functions are implemented in ClassB and they will be triggered by ClassA. So, why the message can be forwarded by DelegateProxy?

When you setup a proxy, you should implement two functions:

class RxClassADelegateProxy: DelegateProxy<ClassA, DelegateA>, DelegateProxyType, DelegateA {
  // ...
  static func currentDelegate(for object: ClassA) -> DelegateA? {
    return object.delegate
  }
  static func setCurrentDelegate(_ delegate: DelegateA?, to object: ClassA) {
    object.delegate = delegate
  }
}

In the init function, setCurrentDelegate(_:to:) is saved as __setCurrentDelegateTo:

public init<Proxy: DelegateProxyType>(parentObject: ParentObject, delegateProxy: Proxy.Type)
            where Proxy: DelegateProxy<ParentObject, Delegate>, Proxy.ParentObject == ParentObject, Proxy.Delegate == Delegate {
              self._parentObject = parentObject
              self._currentDelegateFor = delegateProxy._currentDelegate
              self._setCurrentDelegateTo = delegateProxy._setCurrentDelegate

              MainScheduler.ensureRunningOnMainThread()
              #if TRACE_RESOURCES
                _ = Resources.incrementTotal()
            #endif
            super.init()
}

If a delegate method is subscribed, a new instance of MessageDispather is created. _setCurrentDelegateTo which set ProxyDelegate as delegate of parentObject will be invoked as well.

// Rx/RxCocoa/DelegateProxy.swift

private final class MessageDispatcher {
    private let dispatcher: PublishSubject<[Any]>
    private let result: Observable<[Any]>
  // ...
    init<P, D>(selector: Selector, delegateProxy _delegateProxy: DelegateProxy<P, D>) {
        // ...
        result = dispatcher
            .do(onSubscribed: { weakDelegateProxy?.checkSelectorIsObservable(selector); weakDelegateProxy?.reset() }, onDispose: { weakDelegateProxy?.reset() })
            .share()
            .subscribeOn(mainScheduler)
    }
    // ...
}

Class MessageDispatcher has a dispatcher, an instance of PublishSubject, and result, an observable of dispatcher, which you can subscribe it to get events of delegate.

// Rx/RxCocoa/DelegateProxy.swift

open class DelegateProxy<P: AnyObject, D>: _RXDelegateProxy {
  // ...
  fileprivate func reset() {
    guard let parentObject = self._parentObject else { return }

    let maybeCurrentDelegate = self._currentDelegateFor(parentObject)

    if maybeCurrentDelegate === self {
      self._setCurrentDelegateTo(nil, parentObject)
      self._setCurrentDelegateTo(castOrFatalError(self), parentObject)
    }
  }
  // ...
}

For now, our delegate proxy can receive the message send to the real delegate. But, methods in real delegate are not defined in delegate proxy. So, all messages will be sent to forwardInvocation(_:) defined in _RXDelegateProxy.m. If you don't have idea on what I am talking about, please refer to Objective-C runtime.

-(void)forwardInvocation:(NSInvocation *)anInvocation {
    BOOL isVoid = RX_is_method_signature_void(anInvocation.methodSignature);
    NSArray *arguments = nil;

    if (isVoid) {
        arguments = RX_extract_arguments(anInvocation);
        [self _sentMessage:anInvocation.selector withArguments:arguments];
    }
  // call method that implement in the real delegate, see `Has a Return Value`
    if (self._forwardToDelegate && [self._forwardToDelegate respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self._forwardToDelegate];
    }

    if (isVoid) {
        [self _methodInvoked:anInvocation.selector withArguments:arguments];
    }
}

Notes:

  • _sentMessage:withArguments: and _methodInvoked:withArguments: will be invoked if the return type of method is void.
  • _sentMessage:withArguments: is performed before the message forwards to delegate, and _methodInvoked:withArguments: after that.

Why void methods are differentiated from others? Comments from RxSwift give answer:

Only methods that have void return value can be observed using this method because those methods are used as a notification mechanism. It doesn't matter if they are optional or not. Observing is performed by installing a hidden associated PublishSubject that is used to dispatch messages to observers.

Delegate methods that have non void return value can't be observed directly using this method because:

  • those methods are not intended to be used as a notification mechanism, but as a behavior customization mechanism
  • there is no sensible automatic way to determine a default return value

As we described below, delegate proxy has the ability to forward delegate method. The next question is how to convert a delegate method to observable. The key is _sentMessage & _methodInvoked. Then emit a next element to dispatcher defined in MessageDispatcher when it was called.

// Very Important!!!! These methods ensure that developer could subscribe delegate method in xxx+Rx.swift by sendMessage or methodInvoked.
open override func _sentMessage(_ selector: Selector, withArguments arguments: [Any]) {
    _sentMessageForSelector[selector]?.on(.next(arguments))
}

open override func _methodInvoked(_ selector: Selector, withArguments arguments: [Any]) {
    _methodInvokedForSelector[selector]?.on(.next(arguments))
}

But how MessageDispatcher is initialized? You may find the answer in sentMessage(_:) & methodInvoked(_:).

open func sentMessage(_ selector: Selector) -> Observable<[Any]> {
    MainScheduler.ensureRunningOnMainThread()

    let subject = _sentMessageForSelector[selector]

    if let subject = subject {
        return subject.asObservable()
    } else {
        let subject = MessageDispatcher(selector: selector, delegateProxy: self)
        _sentMessageForSelector[selector] = subject
        return subject.asObservable()
    }
}

open func methodInvoked(_ selector: Selector) -> Observable<[Any]> {
    MainScheduler.ensureRunningOnMainThread()

    let subject = _methodInvokedForSelector[selector]

    if let subject = subject {
        return subject.asObservable()
    } else {
        let subject = MessageDispatcher(selector: selector, delegateProxy: self)
        _methodInvokedForSelector[selector] = subject
        return subject.asObservable()
    }

_sentMessageForSelector and _methodInvokedForSelector is a dictionary to map a selector for delegate to an instance of MessageDispatcher, and they will create one if the MessageDispatcher is not existed.

private var _sentMessageForSelector = [Selector: MessageDispatcher]()
private var _methodInvokedForSelector = [Selector: MessageDispatcher]()

methodInvoked(_:) and sentMessage(_:) returns result, an member variable defined in MessageDispather we discussed above. And now, a delegate method was converted to observable.

Conclusion

Discovering how RxSwift works is an nice exprience for me. I learned how to wrap a regular method to observable stream, and many underlying knowledges about iOS and programming language, such as Objective-C Runtime. Source code, in fact, is not too difficult to read if you determined to do it. You will understand what mechanisms are behind Apis and work with them more elegantly once mastered each part of source code.

All rights reserved
Except where otherwise noted, content on this page is copyrighted.