Many people who are new to RxSwift will ask questions about Dispose Bag. So, what is a Dispose Bag? How to use them? We will find out in this article.
Concept
First, let’s talk about Disposable. Disposable is not a standard concept in general iOS programming, it is used in RxSwift and is the way RxSwift manages its memory. This article will answer some questions about Disposable as well as ARC memory management mechanism and how to avoid Memory Leak while using Rx.
Observable and memory management
RxSwift is a library that helps us solve asynchronous events, so reference counting (anyone who has read about ARC in iOS will understand this concept) is what we need. mind. To better understand let’s analyze the example below
Cancel or NOT cancel? Imagine we have an Observable that is responsible for calling the API. When you call a subcribe (usually located in ViewDidLoad), it will send the request to the server and wait for a response. This is a simple and typical case, but please note, users can return to the previous screen at any time. With the normal memory management mechanism, when returning to the previous screen, the current UIViewController will be deallocate and will also cancel the Observable because it has lost the link from UIViewController. And of course, our request will not be complete in that case. In some cases, you want the Observable to exist until the request is completed and receive a response, even if the user has returned to the previous screen. Therefore, the developer should decide when Observable will be canceled.
Memory is limited resources
Observables can hold a number of variables that are passed in or declared when defining them. That means Observable will assign a piece of memory to store those values. On the other hand, a property of Observable is that it will stop sending events when it receives an error or is completed. Meanwhile, the storage of Observable resources is not necessary, it is best to free up the memory that Observable holds. To do that, we need to be able to “clean” Observable when required. That’s why the Subcribe Method will always return Disposable
Disposable
Disposable is a protocol with a dispose () method. When subcribe to an Observable, the Disposable will keep a strong link to the Observable, and the Observable will also hold a strong link that points back to the Disposable. Thanks to that, if the user has back to the previous screen, Observable will not be deallocated unless you want it deallocated. To break this type of retain cycle, you must call the dispose function in Observable. If the Observable terminates itself (emit out completed or error), it will automatically break retain cycle. In other cases, the responsibility to call the dispose function is done by us.
The simplest way to call dispose in the denit function of ViewController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">MainController</span> <span class="token punctuation">:</span> <span class="token builtin">UIViewController</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> subscription <span class="token punctuation">:</span> <span class="token builtin">Disposable</span> <span class="token operator">?</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> subscription <span class="token operator">=</span> <span class="token function">theObservable</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">subscribe</span> <span class="token punctuation">(</span> onNext <span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token comment">// handle your subscription</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">deinit</span> <span class="token punctuation">{</span> subscription <span class="token operator">?</span> <span class="token punctuation">.</span> <span class="token function">dispose</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
The solution is simple, but imagine how many dispose () lines will need to be added to denit () when you subcribe a lot of Observable. The extension becomes more complicated when you have to change the code in many places. To improve that, you can use the [Disposable] array. Usage as follows
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">MainController</span> <span class="token punctuation">:</span> <span class="token builtin">UIViewController</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> subscriptions <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token builtin">Disposable</span> <span class="token punctuation">]</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> subscriptions <span class="token punctuation">.</span> <span class="token function">append</span> <span class="token punctuation">(</span> <span class="token function">theObservable</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">subscribe</span> <span class="token punctuation">(</span> onNext <span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token function">print</span> <span class="token punctuation">(</span> $ <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">deinit</span> <span class="token punctuation">{</span> subscriptions <span class="token punctuation">.</span> forEach <span class="token punctuation">{</span> $ <span class="token number">0</span> <span class="token punctuation">.</span> <span class="token function">dispose</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Looks much better, the extension is simpler. However, we can improve the code even further. We will use DisposeBag instead of [Disposable]
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">MainController</span> <span class="token punctuation">:</span> <span class="token builtin">UIViewController</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> disposeBag <span class="token operator">=</span> <span class="token function">DisposeBag</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token function">theObservable</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">subscribe</span> <span class="token punctuation">(</span> onNext <span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token comment">// handle your subscription</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">disposed</span> <span class="token punctuation">(</span> by <span class="token punctuation">:</span> disposeBag <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Wait. Why not write in denit anymore. So when will it call to dispose ??? DisposedBag will call to dispose when the viewcontroller containing it is denit. DisposeBag will unlink from UIViewController to it => ARC = 0 and it is dellocated and calls all disposables.
DisposeBag and Retain Cycle
With DisposedBag, if not careful you will easily create Retain Cycles between Observable and UIViewController. Meanwhile, DisposeBag will wait to be dellocate forever and of course not dispose away any of its disposables. What you need to keep in mind is that for each operator it will default to holding strong links to any variable used in its closure. See the example below to better understand
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">MainController</span> <span class="token punctuation">:</span> <span class="token builtin">UIViewController</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">let</span> disposeBag <span class="token operator">=</span> <span class="token function">DisposeBag</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token keyword">let</span> parser <span class="token operator">=</span> <span class="token function">MyModelParser</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">viewDidLoad</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> parsedObject <span class="token operator">=</span> theObservable <span class="token punctuation">.</span> <span class="token builtin">map</span> <span class="token punctuation">{</span> json <span class="token keyword">in</span> <span class="token keyword">return</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> parser <span class="token punctuation">.</span> <span class="token function">parse</span> <span class="token punctuation">(</span> json <span class="token punctuation">)</span> <span class="token punctuation">}</span> parsedObject <span class="token punctuation">.</span> <span class="token function">subscribe</span> <span class="token punctuation">(</span> onNext <span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token number">_</span> <span class="token keyword">in</span> <span class="token comment">//do something</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">disposed</span> <span class="token punctuation">(</span> by <span class="token punctuation">:</span> disposeBag <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
The above code caused Retain Cycles, the reason is because adding Disposable to DisposeBag means that DisposeBag will hold a strong link to Disposbale. Disposable keeps Observable alive. Observable again has a strong link to VIewController because self is used inside the map closure. And finally ViewController has a strong link to DisposeBag. BOOM !. You already have a retain cycle.
To solve that phenomenon, we can use Capture list or [weak self] and [unowned self]. A few small changes may solve your problem
1 2 3 4 5 | <span class="token keyword">let</span> parsedObject <span class="token operator">=</span> theObservable <span class="token punctuation">.</span> <span class="token builtin">map</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span> <span class="token keyword">weak</span> <span class="token keyword">self</span> <span class="token punctuation">]</span> json <span class="token keyword">in</span> <span class="token keyword">return</span> <span class="token keyword">self</span> <span class="token operator">?</span> <span class="token punctuation">.</span> parser <span class="token punctuation">.</span> <span class="token function">parse</span> <span class="token punctuation">(</span> json <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Use self! = Retain cycle
Not just using self will cause the retain cycle, but it usually happens when self also holds DisposeBag.
summary
DisposeBag is not something magical but simply an array with many Disposable inside. It helps us automatically dispose the disposables without having to write manually. However, pay attention to the Retain Cycle phenomenon that it may cause.
Reference source: http://adamborek.com/memory-managment-rxswift/