Memory leaks often occur without any notice. Although using a weak reference for self in closures helps a lot, but that is not enough. We can use memory graph debugging or Xcode Instruments to find and resolve memory errors. But it is quite complicated and time consuming.
Thankfully we have a simpler way, by using unit tests. This method does not prevent all leaks, but it is still very effective.
Prevent memory leak with unit tests
Writing a unit test by combining a weak reference with an autoreleasepool will help determine the release (dealloc) easier. It can check to see if the deinit of a class has been called and the memory has been freed.
In the example below, we will check to see if a view controller has been released. By creating an extension method in XCTestCase
, we can easily add it to any view controller unit test. Besides, it is a good way to check if the view controller has been released properly.
1 2 3 4 5 6 7 8 9 | <span class="token comment">/// Ensures that the OwnedBucketViewController gets deallocated after being added to the navigation stack, then popped.</span> <span class="token keyword">func</span> <span class="token function">testDeallocation</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> assertDeallocation <span class="token punctuation">{</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">UIViewController</span> <span class="token keyword">in</span> <span class="token keyword">let</span> bucket <span class="token operator">=</span> <span class="token function">Bucket</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> viewModel <span class="token operator">=</span> <span class="token function">OwnedBucketViewModel</span> <span class="token punctuation">(</span> bucket <span class="token punctuation">:</span> bucket <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">OwnedBucketViewController</span> <span class="token punctuation">(</span> viewModel <span class="token punctuation">:</span> viewModel <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
This extension creates a weak reference of the view controller created in the closure. Then, we present and dismiss that controller to check that the weak reference has become nil
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <span class="token keyword">extension</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token comment">/// Verifies whether the given constructed UIViewController gets deallocated after being presented and dismissed.</span> <span class="token comment">///</span> <span class="token comment">/// - Parameter testingViewController: The view controller constructor to use for creating the view controller.</span> <span class="token keyword">func</span> <span class="token function">assertDeallocation</span> <span class="token punctuation">(</span> of testedViewController <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">UIViewController</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> weakReferenceViewController <span class="token punctuation">:</span> <span class="token builtin">UIViewController</span> <span class="token operator">?</span> <span class="token keyword">let</span> autoreleasepoolExpectation <span class="token operator">=</span> <span class="token function">expectation</span> <span class="token punctuation">(</span> description <span class="token punctuation">:</span> <span class="token string">"Autoreleasepool should drain"</span> <span class="token punctuation">)</span> autoreleasepool <span class="token punctuation">{</span> <span class="token keyword">let</span> rootViewController <span class="token operator">=</span> <span class="token function">UIViewController</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token comment">// Make sure that the view is active and we can use it for presenting views.</span> <span class="token keyword">let</span> window <span class="token operator">=</span> <span class="token function">UIWindow</span> <span class="token punctuation">(</span> frame <span class="token punctuation">:</span> <span class="token function">CGRect</span> <span class="token punctuation">(</span> x <span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">,</span> y <span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">,</span> width <span class="token punctuation">:</span> <span class="token number">400</span> <span class="token punctuation">,</span> height <span class="token punctuation">:</span> <span class="token number">400</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> window <span class="token punctuation">.</span> rootViewController <span class="token operator">=</span> rootViewController window <span class="token punctuation">.</span> <span class="token function">makeKeyAndVisible</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token comment">/// Present and dismiss the view after which the view controller should be released.</span> rootViewController <span class="token punctuation">.</span> <span class="token function">present</span> <span class="token punctuation">(</span> <span class="token function">testedViewController</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> animated <span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">,</span> completion <span class="token punctuation">:</span> <span class="token punctuation">{</span> weakReferenceViewController <span class="token operator">=</span> rootViewController <span class="token punctuation">.</span> presentedViewController <span class="token function">XCTAssertNotNil</span> <span class="token punctuation">(</span> weakReferenceViewController <span class="token punctuation">)</span> rootViewController <span class="token punctuation">.</span> <span class="token function">dismiss</span> <span class="token punctuation">(</span> animated <span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">,</span> completion <span class="token punctuation">:</span> <span class="token punctuation">{</span> autoreleasepoolExpectation <span class="token punctuation">.</span> <span class="token function">fulfill</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 punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">wait</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> <span class="token punctuation">:</span> <span class="token punctuation">[</span> autoreleasepoolExpectation <span class="token punctuation">]</span> <span class="token punctuation">,</span> timeout <span class="token punctuation">:</span> <span class="token number">10.0</span> <span class="token punctuation">)</span> <span class="token function">wait</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> <span class="token punctuation">:</span> weakReferenceViewController <span class="token operator">==</span> <span class="token constant">nil</span> <span class="token punctuation">,</span> timeout <span class="token punctuation">:</span> <span class="token number">3.0</span> <span class="token punctuation">,</span> description <span class="token punctuation">:</span> <span class="token string">"The view controller should be deallocated since no strong reference points to it."</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">/// Checks for the callback to be the expected value within the given timeout.</span> <span class="token comment">///</span> <span class="token comment">/// - Parameters:</span> <span class="token comment">/// - condition: The condition to check for.</span> <span class="token comment">/// - timeout: The timeout in which the callback should return true.</span> <span class="token comment">/// - description: A string to display in the test log for this expectation, to help diagnose failures.</span> <span class="token keyword">func</span> <span class="token function">wait</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> condition <span class="token punctuation">:</span> @autoclosure @escaping <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">,</span> timeout <span class="token punctuation">:</span> <span class="token builtin">TimeInterval</span> <span class="token punctuation">,</span> description <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">,</span> file <span class="token punctuation">:</span> <span class="token builtin">StaticString</span> <span class="token operator">=</span> #file <span class="token punctuation">,</span> line <span class="token punctuation">:</span> <span class="token builtin">UInt</span> <span class="token operator">=</span> #line <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> end <span class="token operator">=</span> <span class="token function">Date</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">addingTimeInterval</span> <span class="token punctuation">(</span> timeout <span class="token punctuation">)</span> <span class="token keyword">var</span> value <span class="token punctuation">:</span> <span class="token builtin">Bool</span> <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token keyword">let</span> closure <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Void</span> <span class="token operator">=</span> <span class="token punctuation">{</span> value <span class="token operator">=</span> <span class="token function">condition</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token operator">!</span> value <span class="token operator">&&</span> <span class="token number">0</span> <span class="token operator"><</span> end <span class="token punctuation">.</span> timeIntervalSinceNow <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token builtin">RunLoop</span> <span class="token punctuation">.</span> current <span class="token punctuation">.</span> <span class="token function">run</span> <span class="token punctuation">(</span> mode <span class="token punctuation">:</span> <span class="token builtin">RunLoop</span> <span class="token punctuation">.</span> <span class="token builtin">Mode</span> <span class="token punctuation">.</span> <span class="token keyword">default</span> <span class="token punctuation">,</span> before <span class="token punctuation">:</span> <span class="token function">Date</span> <span class="token punctuation">(</span> timeIntervalSinceNow <span class="token punctuation">:</span> <span class="token number">0.002</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">Thread</span> <span class="token punctuation">.</span> <span class="token function">sleep</span> <span class="token punctuation">(</span> forTimeInterval <span class="token punctuation">:</span> <span class="token number">0.002</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">closure</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">closure</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token function">XCTAssertTrue</span> <span class="token punctuation">(</span> value <span class="token punctuation">,</span> <span class="token string">"➡️? Timed out waiting for condition to be true: " <span class="token interpolation"><span class="token delimiter variable">(</span> description <span class="token delimiter variable">)</span></span> ""</span> <span class="token punctuation">,</span> file <span class="token punctuation">:</span> file <span class="token punctuation">,</span> line <span class="token punctuation">:</span> line <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
If the view controller is held down, the logic is in trouble and the test fails.
To confirm whether the weak reference is nil
or not, we use another extension method in XCTestCase
very handy when testing a certain condition.
XCTest
API provides a nice API for generating expectation for notifications, predicates or KVO, but cannot use it to confirm whether a weak reference is really nil
or not. The new extension method will do that. It will check in a period of time to see if the given condition is satisfied or not.
The use of an autoreleasepool
Without autoreleasepool, one cannot check if a weak reference has actually been released. All references in the autoreleasepool closure will be released when they drain, if no strong reference exists.