ARC in Objective-C and swift is a nice technology, between manual memory management and a garbage collector. But it’s not fool-proof and you can still create retain cycles when two objects retain each other. There are numerous blog posts online explaining this problem.
It would be nice to have unit tests to verify your classes don’t have this issue, wouldn’t it? There’s a way to do that.
ReactiveSwift has the Lifetime object to track the deallocation of other objects and provide reactive properties for them. We can use it to write a unit-test to verify that an object is indeed deallocated at the end of the test and there are no internal retain cycles related to the object under test. I’ve used this approach in the project I’m working on to ensure new objects are deallocated after use.
I came up with this assertObjectIsDeallocated assertion that takes an optional object and its name and sets up the lifetime observation to wait until the object is deallocated:
123456789101112
funcassertObjectIsDeallocated(_object:AnyObject?,objectName:String){object.map(Lifetime.of).map{(lifetime:Lifetime)inif!lifetime.hasEnded{letlifetimeEndedExpectation=expectation(description:"\(objectName) is deallocated")lifetime.observeEnded{lifetimeEndedExpectation.fulfill()}waitForExpectations(timeout:1.0)}}}
The sample project is available in this repository: https://github.com/eunikolsky/TestingRetainCycles. You can find a MainViewController that uses a Downloader and the view controller is the downloader’s delegate, which creates a possibility of a retain cycle if not done correctly. The test function itself only starts the download, but doesn’t assert anything; the deallocation assertion is automatically done after each test in tearDown:
This commit has the retain cycle and the test fails:
1
TestingRetainCycles/TestingRetainCyclesTests/TestingRetainCyclesTests.swift:39:error:-[TestingRetainCyclesTests.TestingRetainCyclesTeststestViewControllerShouldBeDeallocated]:Asynchronouswaitfailed:Exceededtimeoutof1seconds,withunfulfilledexpectations:"MainViewController is deallocated".
Next commit has the fix, which is making the Downloader’s delegate property weak and the test now passes: