Memory management in RxSwift - DisposeBag
I’ve noticed a lot of beginners in RxSwift ask about DisposeBag.
DisposeBag isn’t a standard thing in iOS development neither in other Rx’s implementations. Basically, it is how RxSwift handles memory management on iOS platform.
In this article, I want to answer for few question like what is the
Disposable and to talk generally about ARC memory management with RxSwift and how to protect yourself from memory leaks while using RxSwift. I hope you will enjoy it 📚💪
Observable && memory management
When it comes to implementing a library which serves for handling asynchronous events there are few … things you need to be aware of because of iOS reference counting.
The easiest way to describe the problem is to describe it by an example:
To cancel or not to cancel? 🤔
Imagine we have an
Observable which represent a REST API call. When you call
subscribe it sends a request to a server and waits for the response. Let’s say you
subscribe for it in
It’s an easy example, but you need to have that in mind User can come back in navigation stack in any moment. With "normal" memory management coming back to the previous screen would deallocate the
UIViewController and … would also cancel the Observable because it would lose the reference from the UIViewController.
As a result, our request wouldn’t have a chance to finish.
Sometimes it’s an expected behavior, however, sometimes you would like to wait until you receive the response, despite the fact user’s gone back in the navigation stack. The developer should be able to decide when the Observable is terminated.
Memory is a limited resource
Another thing about memory management is that memory is a limited resource. Observables can store some variables inside theirs implementations or they can also store what you have passed to them.
It means it is possible the Observable allocates some part of the memory for its internal needs.
On the other hand, as you probably know, one of the Observable characteristics is after it receives
error event it stops sending events.
If it’s not going to send new events anymore what’s the point of keeping its internal resources? A good idea would be to clean and free the memory the Observable keeps.
To be able to clean the Observable we need to have a possibility to clean the Observable on demand. This is why the
subscribe method returns the
retainCount. Every strong reference to the object increases its
retainCountby one. When a reference is deleted the
retainCountis decreased by one. When
retainCountof an object reaches 0 then the object is deallocated.
Disposable – the type the story begins from
Disposable is just a protocol with one method
When you subscribe for an Observable the
Disposable keeps a reference to the
Observable and the Observable keeps a strong reference to the
Disposable (Rx creates some kind of a retain cycle here). Thanks to that if user navigates back in navigation stack the
Observable won’t be deallocated unless you want it to be deallocated.
To break the retain cycle somebody needs to call
dispose on the Observable. If Observable ends by itself (by sending
error) it will automatically break the retain cycle. In any other scenario, the responsibility to call the
dispose function is in our hands.
The easiest way would be to call
This solution is simple however, it isn’t scalable. Imagine how many additional fields you would need to have in your class.
To improve it, you can have an array of disposables
[Disposable] and goes through all the array and calling
dispose on every
Disposable inside it:
It looks much better now and it’s scalable. No matter how many subscriptions you have
deinit looks the same.
However, this is not the end of improvements. Current solution forces you to remember about manually disposing in
Yes, you probably know where it is going. We can use
DisposeBag instead of
Wait a minute! Where did the
The cool thing about
DisposeBag is it takes care of calling the
dispose on every disposable inside it.
When it calls dispose you ask?
dispose when it’s own
deinit is called. It means when
DisposeBag loses a reference from
UIViewController its retainCount goes to 0 so it will be deallocated and it will call
dispose on all the disposables.
DisposeBag && retain cycle 😱
dispose on deinit is the simplest way to clean the memory, however … it will only work if the
deinit will be called.
DiposeBag it is easy to bring about a retain cycles between Observables and
UIViewController. DisposeBag will wait for dealloc forever and will never dispose its disposables.
What you need to remember is every operator by default keeps a strong reference to every variable used in its closure:
In above example,
transformedObservable keeps a strong reference to
self was used in
map operator. Such a behavior is a natural way how Swift uses reference counting to ensure everything will be allocated when it’s needed.
The code above doesn’t create a retain cycle. Unfortunately, with few changes retain cycle becomes a real problem:
Lines which cause the retain cycle are
.disposed(by: disposeBag) & the
Because of adding the Disposable into the DisposeBag it means the DisposeBag have a strong reference to Disposable.
The Disposable keeps Observable alive which hold a strong reference to MyViewController because
self was used inside
At the end the ViewController holds a reference to the DisposeBag and … 💥💥BOOM💥💥 … You have a retain cycle! 😱
I recommend you to draw a diagram if you can’t see the dependencies:
How to avoid a retain cycle?
I need to say with a good design you going to have less and less possible areas to have a retain cycle there. Good separation of concerns is a key here.
The code above is too small to talk about architecture patterns. How to get rid of the retain cycle in this case?
Just use a capture list!
With capture list, you can pass a variable and tell the compiler how a closure should treat the variable in case of memory. Usually, the first idea is to pass
However, if we use
[weak] then we need to tell the compiler what should be returned if self is nil. In this case is better to pass the
parser in capture list instead of
Swift allows us to pass a variable into the capture list without any attribute like
unowned. If we do so, the compiler knows to keep a reference only to the parser (with strong reference), not to
This is it. Only one small change solves the entire problem! 💪
[unowned self]instead of
[weak self]. However, I’m not a fan of using an
unownedattribute. If something goes wrong you will have a crash 😟.
Using self != retain cycle
Now as we all know that every operator keeps strong reference to every variable in its closure including
self, I want to emphasize that we don’t need to avoid using
If you have a class which just returns an Observable, it’s fine to use
self in operators which created the Observable.
For example if you have a
UIViewController which has
APIClient as a dependency it’s ok to use
The rule of thumb is retain cycle usually occurs when
self is also the owner of the DisposeBag. In any other case it’s rather safe to use
self in operators closures.
I hope the
DisposeBag isn’t anything magical now for you. It’s just an array with multiple
Disposable inside. It’s a nice helper to dispose all the disposables in
deinit. Otherwise, our life would be much harder.
Unfortunately, using a
DisposeBag sometimes leads to memory leaks. Remember that every operator keeps a strong reference to dependencies used in its closure. If it is
self it is also kept by the Observable. As a result, you have a retain cycle.
If you add a
Disposable into the
DisposeBag just use capture list and pass proper variables and attributes to closures. This is the way how you can avoid retain cycles.