NSAssert can cause memory leaks

hsoi blog, talk 0 Comments

I just learned something the hard way: NSAssert() can cause (hard to track down) memory leaks.

They’re hard to track down because 1. you may not always have assertions turned on (e.g. debug vs. release builds), 2. it’s non-obvious in reviewing code.

Note that NSAssert is a macro. Here’s how it expands:

#define NSAssert(condition, desc, ...) 
    do {                
    __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS 
    if (!(condition)) {     
        [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd 
        object:self file:[NSString stringWithUTF8String:__FILE__] 
            lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; 
    }               
        __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS 
    } while(0)

Do you see it?

Do you see where self is referenced?

Typically this is not a problem. But what if you have a block? What if you NSAssert inside that block? That macro will be expanded, self will be captured, and you could wind up with a retain cycle. One that could be difficult to track down.

It also illustrates the importance of not just testing your normal debug code, but periodically testing your release code (or that formal QA should be testing release builds) — i.e. test what you (will) ship. Codepaths and behaviors may be different enough under the release build than your daily debug builds that you may only see problems in one place and not the other. Of course if that happens, it’s a clue to start looking at the key difference, which is the presense or lack of debug infrastructure.

One way to solve it is to not NSAssert but manually make your own checks, perhaps guarded with #if DEBUG or other similar preprocessor checks. You could even write your own version of NSAssert() such as NSAssertWeak() that expands the same but instead captures a weakSelf; you just have to ensure you establish and follow some sort of weakself or weakSelf or wself type of convention.

Or another way? Convert to use Swift (instead of Objective-C), since there assert() is a function, not a macro.

The preprocessor is a powerful feature of C-based languages, but this illustrates one of the possible hidden gotchas that can come with such power.

Leave a Reply