ARC Doesn't Clean Up After an Exception
Apr 6 2013

I recently discovered that ARC does not clean up objects automatically when an exception is thrown, which was the cause of some strange test pollution in my specs.

An example will best illustrate how this can happen. Here’s a simple assertion method that raises an exception if an object is not an instance of a given class:

- (void)assertObject:(id)object isKindOfClass:(Class)class
{
    if (![object isKindOfClass:class])
    {
        [NSException raise:NSInternalInconsistencyException
                  format:@"Expected %@, received %@",
                           NSStringFromClass(class),
                         NSStringFromClass([object class])];
    }
}

When the exception is thrown, object will leak. Here’s why: ARC calls retain on all objects passed to your methods, and pairs them with a release when your method returns. But it doesn’t call release if an exception occurs.

This is not a bug. The ARC documentation explicitly states:

By default in Objective C, ARC is not exception-safe for normal releases:

The reasoning is that exceptions in Objective-C are reserved for very exceptional cases. They are not suitable for control flow or general error handling and typically signal that the app is about to crash.

This assumption falls flat if you’re running automated tests. As the assertObject:isKindOfClass: method implies, Objective-C testing frameworks typically do use exceptions for control flow. As a result, the default behavior can cause your tests to leak memory and cause test pollution.

Luckily there’s a way to turn on ARC exception handling by providing the -fobjc-arc-exceptions flag when compiling. This ended up solving my obscure case of test pollution. If one test failed it would leak a global object and in turn cause others run after it to fail in strange ways.