RxSwift DelegateProxy with required methods

May 12, 2016 - rxswift swift

Once you’ve sipped from the Reactive Kool-Aid it can become emotionally painful to have to implement callback methods the old-fashioned way when you hit a delegate-style API that doesn’t have a prebuilt Observable wrapper.

Max Alexander did a great tutorial post on how to implement your own DelegateProxy for such an API, but unfortunately it won’t work for delegates that that have required methods. A common mistake is to implement the required delegate method in your DelegateProxy class, forward the  call using self._forwardToDelegate, and then call rx_delegate.observe() as before. This will result in the fairly misleading message:

Delegate proxy is already implementing ``xxx:``, a more performant way of registering might exist.

The right way to implement a DelegateProxy for a delegate protocol with required methods involves doing the following (using the Google Identity Toolkit here as an example):

  1. First implement the DelegateProxy as per Max’s instructions, i.e.:
class RxGIDSignInDelegateProxy: DelegateProxy, GIDSignInDelegate, DelegateProxyType {

    static func currentDelegateFor(object: AnyObject) -> AnyObject? {
        let signin = object as! GIDSignIn
        return signin.delegate
    }

    static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
        let signin = object as! GIDSignIn
        signin.delegate = delegate as? GIDSignInDelegate
    }
}
  1. Next declare a local PublishSubject property in your proxy, and publish Next & Error events to the subject within the delegate method. You can see we’re also forwarding to the traditional delegate (if one exists), and for completeness’ sake, sending Completed on deinit.
  let signInSubject = PublishSubject<GIDGoogleUser>()

  func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!, withError error: NSError!) {
      if let u = user {
          signInSubject.on(.Next(u))
      } else if let e = error {
          signInSubject.on(.Error(e))
      }
      self._forwardToDelegate?.signIn(signIn, didSignInForUser: user, withError: error)
  }

  deinit {
      signInSubject.on(.Completed)
  }
  1. At this point, we can implement our rx_* extension method on the relevant class. PublishSubject is a subclass of Observable, so we can just return this value directly.
extension GIDSignIn {
    public var rx_delegate: DelegateProxy {
        return proxyForObject(RxGIDSignInDelegateProxy.self, self)
    }

    public var rx_userDidSignIn: Observable<GIDGoogleUser> {
        let proxy = proxyForObject(RxGIDSignInDelegateProxy.self, self)
        return proxy.signInSubject
    }
    
    // implement your optional delegate methods with `rx_delegate.observe`
}

That’s it! Observe away without sullying your code with delegate callbacks.