Automated iOS Retain Cycle Testing in Objective-C

When developing an iOS app, one needs to watch out for retain cycles that prevent objects from being deallocated. We’re using ReactiveCocoa heavily on my current project, which means we have a lot of blocks in our code, and thus ample opportunity to introduce a retain cycle (see ReactiveCocoa’s Memory Management documentation for more info).

As hard as the team tries, one of us will inevitably forget a strongify in a block, and some object or another will no longer be deallocated when it should be. To try to prevent this from getting out of hand I’ve gotten into the habit of occasionally running the app in Instruments to see if there are more instances of a class hanging around than I expect.

But this kind of manual check can be easy to forget. Especially when coming back to a project for a later round of development sometime down the road. So I wanted to see if there was any way to check for these kind of leaks as part of our automated test suite.

Tracking Allocations and Deallocations

The problem that retain cycles introduce is that an object that is expected to be deallocated hangs around, forever. This can cause a variety of issues, but the point is that the program is not doing what you want it to do. I want each object I allocate to be deallocated when I no longer have a need for it.

If a class could keep track of how many of its instances have not been deallocated at the end of a test, then it would be possible to fail if that number is greater than zero.

By using a static variable as a stand-in for a class variable, we can increment/decrement a counter in a class’ init/dealloc methods.

In the header:

@interface ApplicationModel : NSObject
+ (int32_t)instanceCount;
@end

And the implementation:

static int32_t classInstanceCount = 0;

@implementation ApplicationModel

+ (int32_t)instanceCount {
    return classInstanceCount;
}

- (instancetype)init {
    if (self = [super init]) {
#ifdef DEBUG
        OSAtomicIncrement32Barrier(&classInstanceCount);
#endif    
    }
    return self;
}

- (void)dealloc { 
#ifdef DEBUG
    OSAtomicDecrement32Barrier(&classInstanceCount);
#endif
}
@end  

I only care about tracking the instance count for DEBUG builds of the app, so the increment/decrement code is wrapped in #ifdef blocks. I’m also using Atomic Operations to make sure the counter is incremented and decremented safely in a multithreaded environment.

Ideally there would be a way to apply this pattern to every class automatically, but I’ve yet to come up with anything like that. My only thought so far is to write some C macros to drop the necessary code chunks into place with a bit less repetition.

KIF Tests

We are using the KIF (Keep It Functional) Testing Framework for UI testing, and Expecta for nicer assertions. I’d originally thought that the best place to check the instance count of a class would be in the afterEach of each test. Unfortunately my objects weren’t being deallocated until sometime after the afterEach was executed.

According to a discussion on KIF memory management, KIF wraps each test in an autorelease pool. When each test finishes, the pool is automatically drained and the objects allocated during the test should be deallocated. But they won’t be released until after each test completes.

So instead of checking in the afterEach, I moved the check to the start of the beforeEach. This obviously doesn’t help with the first test, but since we have many tests (both in the suite as a whole and for the most part within a single file), there are plenty of opportunities to check for leaks.

Here is the line (using the Expecta syntax):

  expect([ApplicationModel instanceCount]).will.equal(0);

Note: Expecta’s will causes the expect to wait up to a second for the instanceCount to equal 0 before failing.

If there are any instances of the ApplicationModel class that still have strong references when the test is done running, they won’t be deallocated, which will trigger a failing assertion in the beforeEach of the next test to run.

Conclusion

I think it would be painful to add this kind of tracking to every object in an application. But if there are a number of heavily-used ViewControllers or some key model objects that could automatically be checked for retain cycles, I think this could be very valuable.

We are only just starting to experiment with this kind of tracking/testing on our project now. I’ll follow up to this post sometime down the road with an update on how it works out.

Additional Resources

Conversation
  • Lono says:

    Why not using CFGetRetainCount?

  • Comments are closed.