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:
Does anybody have an idea what the problem might be? Or has anybody successfully implemented something like rx_applicationDidBecomeActive
?
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.