Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transform UIApplicationDelegate methods into RxSwift Observables

In RxSwift / RxCocoa you can create a reactive wrapper for a delegate (e.g. UIScrollViewDelegate or CLLocationManagerDelegate) to enable Rx observable sequences for certain delegate methods.

I am trying to implement this for the UIApplicationDelegate method applicationDidBecomeActive:

What I tried so far is pretty straightforward and similar to the DelegateProxy subclasses that are included in RxCocoa.

I created my DelegateProxy subclass:

class RxUIApplicationDelegateProxy: DelegateProxy, UIApplicationDelegate, DelegateProxyType {

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

    static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
        let application: UIApplication = object as! UIApplication
        application.delegate = delegate as? UIApplicationDelegate
    }
}

And an Rx extension for UIApplication:

extension UIApplication {
    public var rx_delegate: DelegateProxy {
        return proxyForObject(RxUIApplicationDelegateProxy.self, self)
    }

    public var rx_applicationDidBecomeActive: Observable<Void> {
        return rx_delegate.observe("applicationDidBecomeActive:")
            .map { _ in
                return
            }
    }
}

In my AppDelegate I subscribe to the observable:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // the usual setup
    // and then:
    application.rx_applicationDidBecomeActive
        .subscribeNext { _ in
            print("Active!")
        }
        .addDisposableTo(disposeBag)

    return true
}

When I start my app "Active!" gets printed and then I get the following crash in RxCocoa's _RXDelegateProxy_ class:

enter image description here

Does anybody have an idea what the problem might be? Or has anybody successfully implemented something like rx_applicationDidBecomeActive?

like image 861
joern Avatar asked Feb 23 '16 10:02

joern


1 Answers

It looks like a really tricky issue with RxSwift and memory management.

The default implementation of DelegateProxyType sets an instance of a delegate proxy (in this case, RxUIApplicationDelegateProxy) to the delegate of UIApplication.

It also stores the original AppDelegate as a property called forwardToDelegate so all the delegate methods can still be passed to it.

The problem is that, when the new app delegate is set:

 application.delegate = delegate as? UIApplicationDelegate

the original one is deallocated! You can check it by overriding deinit in AppDelegate. The reasons are explained in this answer. And because the property forwardToDelegate is of type assign, your app crashes as the property points to a deallocated object.

I have found a workaround for that. I'm not really sure if it is a recommended way, so be warned. You can override a method from DelegateProxyType in RxUIApplicationDelegateProxy:

  override func setForwardToDelegate(delegate: AnyObject?, retainDelegate: Bool) {
    super.setForwardToDelegate(delegate, retainDelegate: true)
  }

In normal circumstances, you don't want to retain the delegate as it leads to a retain cycle. But in this special case, this is not a problem: your UIApplication object will exist the entire time while your application is alive anyway.

like image 99
Michał Ciuba Avatar answered Sep 26 '22 13:09

Michał Ciuba